diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 5d12634..0000000 --- a/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml deleted file mode 100644 index b654375..0000000 --- a/.github/workflows/web.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: web - -on: - push: - branches: - - src - -jobs: - web: - runs-on: ubuntu-latest - steps: - - name: Checkout source code - uses: actions/checkout@v1 - - name: Use Node.js - uses: actions/setup-node@v1 - with: - node-version: 12.x - - name: Yarn cache - uses: actions/cache@preview - with: - path: ~/.cache/yarn - key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Build the website - run: | - npm install -g yarn - yarn --frozen-lockfile - yarn generate - git checkout master - rm -rf favicon.ico _nuxt robots.txt sw.js index.html credits - cp -r dist/* . - git config user.name "${GITHUB_ACTOR}" - git config user.email "${GITHUB_ACTOR}@noreply.github.com" - git add --all . - git commit -m "Update / @ ${GITHUB_SHA}" - git push "https://${GITHUB_PAT}@github.com/${GITHUB_REPOSITORY}.git" HEAD:master - git checkout "${GITHUB_SHA}" - env: - VUE_APP_GAID: ${{ secrets.VUE_APP_GAID }} - GITHUB_PAT: ${{ secrets.GITHUB_PAT }} diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/200.html b/200.html new file mode 100644 index 0000000..d3e3302 --- /dev/null +++ b/200.html @@ -0,0 +1,9 @@ + + + + python-gino.org + + +
Loading...
+ + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..f179246 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +python-gino.org \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index e07973d..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# python-gino.org - -> python-gino.org - -## Build Setup - -``` bash -# install dependencies -$ yarn install - -# serve with hot reload at localhost:3000 -$ yarn dev - -# build for production and launch server -$ yarn build -$ yarn start - -# generate static project -$ yarn generate -``` - -For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). diff --git a/_nuxt/59fad4f546dd138098ba.js b/_nuxt/59fad4f546dd138098ba.js new file mode 100644 index 0000000..636124a --- /dev/null +++ b/_nuxt/59fad4f546dd138098ba.js @@ -0,0 +1,2 @@ +/*! For license information please see LICENSES */ +(window.webpackJsonp=window.webpackJsonp||[]).push([[1],[,function(t,e,n){"use strict";(function(t,n){var r=Object.freeze({});function o(t){return null==t}function c(t){return null!=t}function f(t){return!0===t}function l(t){return"string"==typeof t||"number"==typeof t||"symbol"==typeof t||"boolean"==typeof t}function d(t){return null!==t&&"object"==typeof t}var h=Object.prototype.toString;function v(t){return"[object Object]"===h.call(t)}function y(t){return"[object RegExp]"===h.call(t)}function m(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function _(t){return c(t)&&"function"==typeof t.then&&"function"==typeof t.catch}function w(t){return null==t?"":Array.isArray(t)||v(t)&&t.toString===h?JSON.stringify(t,null,2):String(t)}function x(t){var e=parseFloat(t);return isNaN(e)?t:e}function O(t,e){for(var map=Object.create(null),n=t.split(","),i=0;i-1)return t.splice(n,1)}}var k=Object.prototype.hasOwnProperty;function C(t,e){return k.call(t,e)}function E(t){var e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}var $=/-(\w)/g,T=E((function(t){return t.replace($,(function(t,e){return e?e.toUpperCase():""}))})),j=E((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),I=/\B([A-Z])/g,P=E((function(t){return t.replace(I,"-$1").toLowerCase()}));var N=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function n(a){var n=arguments.length;return n?n>1?t.apply(e,arguments):t.call(e,a):t.call(e)}return n._length=t.length,n};function L(t,e){e=e||0;for(var i=t.length-e,n=new Array(i);i--;)n[i]=t[i+e];return n}function R(t,e){for(var n in e)t[n]=e[n];return t}function M(t){for(var e={},i=0;i0,at=nt&&nt.indexOf("edge/")>0,st=(nt&&nt.indexOf("android"),nt&&/iphone|ipad|ipod|ios/.test(nt)||"ios"===et),ct=(nt&&/chrome\/\d+/.test(nt),nt&&/phantomjs/.test(nt),nt&&nt.match(/firefox\/(\d+)/)),ut={}.watch,ft=!1;if(Z)try{var lt={};Object.defineProperty(lt,"passive",{get:function(){ft=!0}}),window.addEventListener("test-passive",null,lt)}catch(t){}var pt=function(){return void 0===Y&&(Y=!Z&&!tt&&void 0!==t&&(t.process&&"server"===t.process.env.VUE_ENV)),Y},ht=Z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function vt(t){return"function"==typeof t&&/native code/.test(t.toString())}var yt,mt="undefined"!=typeof Symbol&&vt(Symbol)&&"undefined"!=typeof Reflect&&vt(Reflect.ownKeys);yt="undefined"!=typeof Set&&vt(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var gt=D,bt=0,_t=function(){this.id=bt++,this.subs=[]};_t.prototype.addSub=function(sub){this.subs.push(sub)},_t.prototype.removeSub=function(sub){A(this.subs,sub)},_t.prototype.depend=function(){_t.target&&_t.target.addDep(this)},_t.prototype.notify=function(){var t=this.subs.slice();for(var i=0,e=t.length;i-1)if(c&&!C(o,"default"))f=!1;else if(""===f||f===P(t)){var d=Xt(String,o.type);(d<0||l0&&(ge((r=t(r,(n||"")+"_"+i))[0])&&ge(h)&&(v[d]=Ct(h.text+r[0].text),r.shift()),v.push.apply(v,r)):l(r)?ge(h)?v[d]=Ct(h.text+r):""!==r&&v.push(Ct(r)):ge(r)&&ge(h)?v[d]=Ct(h.text+r.text):(f(e._isVList)&&c(r.tag)&&o(r.key)&&c(n)&&(r.key="__vlist"+n+"_"+i+"__"),v.push(r)));return v}(t):void 0}function ge(t){return c(t)&&c(t.text)&&!1===t.isComment}function be(t,e){if(t){for(var n=Object.create(null),r=mt?Reflect.ownKeys(t):Object.keys(t),i=0;i0,f=t?!!t.$stable:!c,l=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(f&&n&&n!==r&&l===n.$key&&!c&&!n.$hasNormal)return n;for(var d in o={},t)t[d]&&"$"!==d[0]&&(o[d]=Oe(e,d,t[d]))}else o={};for(var h in e)h in o||(o[h]=Se(e,h));return t&&Object.isExtensible(t)&&(t._normalized=o),J(o,"$stable",f),J(o,"$key",l),J(o,"$hasNormal",c),o}function Oe(t,e,n){var r=function(){var t=arguments.length?n.apply(null,arguments):n({});return(t=t&&"object"==typeof t&&!Array.isArray(t)?[t]:me(t))&&(0===t.length||1===t.length&&t[0].isComment)?void 0:t};return n.proxy&&Object.defineProperty(t,e,{get:r,enumerable:!0,configurable:!0}),r}function Se(t,e){return function(){return t[e]}}function Ae(t,e){var n,i,r,o,f;if(Array.isArray(t)||"string"==typeof t)for(n=new Array(t.length),i=0,r=t.length;idocument.createEvent("Event").timeStamp&&(yn=function(){return mn.now()})}function gn(){var t,e;for(vn=yn(),dn=!0,un.sort((function(a,b){return a.id-b.id})),hn=0;hnhn&&un[i].id>t.id;)i--;un.splice(i+1,0,t)}else un.push(t);pn||(pn=!0,ue(gn))}}(this)},_n.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||d(t)||this.deep){var e=this.value;if(this.value=t,this.user)try{this.cb.call(this.vm,t,e)}catch(t){Yt(t,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,t,e)}}},_n.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},_n.prototype.depend=function(){for(var i=this.deps.length;i--;)this.deps[i].depend()},_n.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||A(this.vm._watchers,this);for(var i=this.deps.length;i--;)this.deps[i].removeSub(this);this.active=!1}};var wn={enumerable:!0,configurable:!0,get:D,set:D};function xn(t,e,n){wn.get=function(){return this[e][n]},wn.set=function(t){this[e][n]=t},Object.defineProperty(t,n,wn)}function On(t){t._watchers=[];var e=t.$options;e.props&&function(t,e){var n=t.$options.propsData||{},r=t._props={},o=t.$options._propKeys=[];t.$parent&&Pt(!1);var c=function(c){o.push(c);var f=Wt(c,e,n,t);Rt(r,c,f),c in t||xn(t,"_props",c)};for(var f in e)c(f);Pt(!0)}(t,e.props),e.methods&&function(t,e){t.$options.props;for(var n in e)t[n]="function"!=typeof e[n]?D:N(e[n],t)}(t,e.methods),e.data?function(t){var data=t.$options.data;v(data=t._data="function"==typeof data?function(data,t){xt();try{return data.call(t,t)}catch(e){return Yt(e,t,"data()"),{}}finally{Ot()}}(data,t):data||{})||(data={});var e=Object.keys(data),n=t.$options.props,i=(t.$options.methods,e.length);for(;i--;){var r=e[i];0,n&&C(n,r)||(o=void 0,36!==(o=(r+"").charCodeAt(0))&&95!==o&&xn(t,"_data",r))}var o;Lt(data,!0)}(t):Lt(t._data={},!0),e.computed&&function(t,e){var n=t._computedWatchers=Object.create(null),r=pt();for(var o in e){var c=e[o],f="function"==typeof c?c:c.get;0,r||(n[o]=new _n(t,f||D,D,Sn)),o in t||An(t,o,c)}}(t,e.computed),e.watch&&e.watch!==ut&&function(t,e){for(var n in e){var r=e[n];if(Array.isArray(r))for(var i=0;i-1:"string"==typeof pattern?pattern.split(",").indexOf(t)>-1:!!y(pattern)&&pattern.test(t)}function Ln(t,filter){var e=t.cache,n=t.keys,r=t._vnode;for(var o in e){var c=e[o];if(c){var f=Pn(c.componentOptions);f&&!filter(f)&&Rn(e,o,n,r)}}}function Rn(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,A(n,e)}!function(t){t.prototype._init=function(t){var e=this;e._uid=$n++,e._isVue=!0,t&&t._isComponent?function(t,e){var n=t.$options=Object.create(t.constructor.options),r=e._parentVnode;n.parent=e.parent,n._parentVnode=r;var o=r.componentOptions;n.propsData=o.propsData,n._parentListeners=o.listeners,n._renderChildren=o.children,n._componentTag=o.tag,e.render&&(n.render=e.render,n.staticRenderFns=e.staticRenderFns)}(e,t):e.$options=Vt(Tn(e.constructor),t||{},e),e._renderProxy=e,e._self=e,function(t){var e=t.$options,n=e.parent;if(n&&!e.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(t)}t.$parent=n,t.$root=n?n.$root:t,t.$children=[],t.$refs={},t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(e),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&nn(t,e)}(e),function(t){t._vnode=null,t._staticTrees=null;var e=t.$options,n=t.$vnode=e._parentVnode,o=n&&n.context;t.$slots=_e(e._renderChildren,o),t.$scopedSlots=r,t._c=function(a,b,e,n){return We(t,a,b,e,n,!1)},t.$createElement=function(a,b,e,n){return We(t,a,b,e,n,!0)};var c=n&&n.data;Rt(t,"$attrs",c&&c.attrs||r,null,!0),Rt(t,"$listeners",e._parentListeners||r,null,!0)}(e),cn(e,"beforeCreate"),function(t){var e=be(t.$options.inject,t);e&&(Pt(!1),Object.keys(e).forEach((function(n){Rt(t,n,e[n])})),Pt(!0))}(e),On(e),function(t){var e=t.$options.provide;e&&(t._provided="function"==typeof e?e.call(t):e)}(e),cn(e,"created"),e.$options.el&&e.$mount(e.$options.el)}}(jn),function(t){var e={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(t.prototype,"$data",e),Object.defineProperty(t.prototype,"$props",n),t.prototype.$set=Mt,t.prototype.$delete=del,t.prototype.$watch=function(t,e,n){if(v(e))return En(this,t,e,n);(n=n||{}).user=!0;var r=new _n(this,t,e,n);if(n.immediate)try{e.call(this,r.value)}catch(t){Yt(t,this,'callback for immediate watcher "'+r.expression+'"')}return function(){r.teardown()}}}(jn),function(t){var e=/^hook:/;t.prototype.$on=function(t,n){var r=this;if(Array.isArray(t))for(var i=0,o=t.length;i1?L(n):n;for(var r=L(arguments,1),o='event handler for "'+t+'"',i=0,c=n.length;iparseInt(this.max)&&Rn(c,f[0],f,this._vnode)),t.data.keepAlive=!0}return t||slot&&slot[0]}}};!function(t){var e={get:function(){return W}};Object.defineProperty(t,"config",e),t.util={warn:gt,extend:R,mergeOptions:Vt,defineReactive:Rt},t.set=Mt,t.delete=del,t.nextTick=ue,t.observable=function(t){return Lt(t),t},t.options=Object.create(null),V.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,R(t.options.components,Dn),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var n=L(arguments,1);return n.unshift(this),"function"==typeof t.install?t.install.apply(t,n):"function"==typeof t&&t.apply(null,n),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=Vt(this.options,t),this}}(t),In(t),function(t){V.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&v(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&"function"==typeof n&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}(t)}(jn),Object.defineProperty(jn.prototype,"$isServer",{get:pt}),Object.defineProperty(jn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(jn,"FunctionalRenderContext",{value:Ue}),jn.version="2.6.11";var Fn=O("style,class"),Un=O("input,textarea,option,select,progress"),Bn=O("contenteditable,draggable,spellcheck"),zn=O("events,caret,typing,plaintext-only"),qn=O("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Hn="http://www.w3.org/1999/xlink",Vn=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Kn=function(t){return Vn(t)?t.slice(6,t.length):""},Wn=function(t){return null==t||!1===t};function Gn(t){for(var data=t.data,e=t,n=t;c(n.componentInstance);)(n=n.componentInstance._vnode)&&n.data&&(data=Jn(n.data,data));for(;c(e=e.parent);)e&&e.data&&(data=Jn(data,e.data));return function(t,e){if(c(t)||c(e))return Xn(t,Yn(e));return""}(data.staticClass,data.class)}function Jn(t,e){return{staticClass:Xn(t.staticClass,e.staticClass),class:c(t.class)?[t.class,e.class]:e.class}}function Xn(a,b){return a?b?a+" "+b:a:b||""}function Yn(t){return Array.isArray(t)?function(t){for(var e,n="",i=0,r=t.length;i-1?Or(t,e,n):qn(e)?Wn(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Bn(e)?t.setAttribute(e,function(t,e){return Wn(e)||"false"===e?"false":"contenteditable"===t&&zn(e)?e:"true"}(e,n)):Vn(e)?Wn(n)?t.removeAttributeNS(Hn,Kn(e)):t.setAttributeNS(Hn,e,n):Or(t,e,n)}function Or(t,e,n){if(Wn(n))t.removeAttribute(e);else{if(ot&&!it&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var Sr={create:wr,update:wr};function Ar(t,e){var n=e.elm,data=e.data,r=t.data;if(!(o(data.staticClass)&&o(data.class)&&(o(r)||o(r.staticClass)&&o(r.class)))){var f=Gn(e),l=n._transitionClasses;c(l)&&(f=Xn(f,Yn(l))),f!==n._prevClass&&(n.setAttribute("class",f),n._prevClass=f)}}var kr,Cr={create:Ar,update:Ar};function Er(t,e,n){var r=kr;return function o(){var c=e.apply(null,arguments);null!==c&&jr(t,o,n,r)}}var $r=ne&&!(ct&&Number(ct[1])<=53);function Tr(t,e,n,r){if($r){var o=vn,c=e;e=c._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return c.apply(this,arguments)}}kr.addEventListener(t,e,ft?{capture:n,passive:r}:n)}function jr(t,e,n,r){(r||kr).removeEventListener(t,e._wrapper||e,n)}function Ir(t,e){if(!o(t.data.on)||!o(e.data.on)){var n=e.data.on||{},r=t.data.on||{};kr=e.elm,function(t){if(c(t.__r)){var e=ot?"change":"input";t[e]=[].concat(t.__r,t[e]||[]),delete t.__r}c(t.__c)&&(t.change=[].concat(t.__c,t.change||[]),delete t.__c)}(n),he(n,r,Tr,jr,Er,e.context),kr=void 0}}var Pr,Nr={create:Ir,update:Ir};function Lr(t,e){if(!o(t.data.domProps)||!o(e.data.domProps)){var n,r,f=e.elm,l=t.data.domProps||{},d=e.data.domProps||{};for(n in c(d.__ob__)&&(d=e.data.domProps=R({},d)),l)n in d||(f[n]="");for(n in d){if(r=d[n],"textContent"===n||"innerHTML"===n){if(e.children&&(e.children.length=0),r===l[n])continue;1===f.childNodes.length&&f.removeChild(f.childNodes[0])}if("value"===n&&"PROGRESS"!==f.tagName){f._value=r;var h=o(r)?"":String(r);Rr(f,h)&&(f.value=h)}else if("innerHTML"===n&&er(f.tagName)&&o(f.innerHTML)){(Pr=Pr||document.createElement("div")).innerHTML=""+r+"";for(var svg=Pr.firstChild;f.firstChild;)f.removeChild(f.firstChild);for(;svg.firstChild;)f.appendChild(svg.firstChild)}else if(r!==l[n])try{f[n]=r}catch(t){}}}}function Rr(t,e){return!t.composing&&("OPTION"===t.tagName||function(t,e){var n=!0;try{n=document.activeElement!==t}catch(t){}return n&&t.value!==e}(t,e)||function(t,e){var n=t.value,r=t._vModifiers;if(c(r)){if(r.number)return x(n)!==x(e);if(r.trim)return n.trim()!==e.trim()}return n!==e}(t,e))}var Mr={create:Lr,update:Lr},Dr=E((function(t){var e={},n=/:(.+)/;return t.split(/;(?![^(]*\))/g).forEach((function(t){if(t){var r=t.split(n);r.length>1&&(e[r[0].trim()]=r[1].trim())}})),e}));function Fr(data){var style=Ur(data.style);return data.staticStyle?R(data.staticStyle,style):style}function Ur(t){return Array.isArray(t)?M(t):"string"==typeof t?Dr(t):t}var Br,zr=/^--/,qr=/\s*!important$/,Hr=function(t,e,n){if(zr.test(e))t.style.setProperty(e,n);else if(qr.test(n))t.style.setProperty(P(e),n.replace(qr,""),"important");else{var r=Kr(e);if(Array.isArray(n))for(var i=0,o=n.length;i-1?e.split(Gr).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" "+(t.getAttribute("class")||"")+" ";n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function Xr(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(Gr).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{for(var n=" "+(t.getAttribute("class")||"")+" ",r=" "+e+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?t.setAttribute("class",n):t.removeAttribute("class")}}function Yr(t){if(t){if("object"==typeof t){var e={};return!1!==t.css&&R(e,Qr(t.name||"v")),R(e,t),e}return"string"==typeof t?Qr(t):void 0}}var Qr=E((function(t){return{enterClass:t+"-enter",enterToClass:t+"-enter-to",enterActiveClass:t+"-enter-active",leaveClass:t+"-leave",leaveToClass:t+"-leave-to",leaveActiveClass:t+"-leave-active"}})),Zr=Z&&!it,to="transition",eo="transitionend",no="animation",ro="animationend";Zr&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(to="WebkitTransition",eo="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(no="WebkitAnimation",ro="webkitAnimationEnd"));var oo=Z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function io(t){oo((function(){oo(t)}))}function ao(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),Jr(t,e))}function so(t,e){t._transitionClasses&&A(t._transitionClasses,e),Xr(t,e)}function co(t,e,n){var r=fo(t,e),o=r.type,c=r.timeout,f=r.propCount;if(!o)return n();var l="transition"===o?eo:ro,d=0,h=function(){t.removeEventListener(l,v),n()},v=function(e){e.target===t&&++d>=f&&h()};setTimeout((function(){d0&&(n="transition",v=f,y=c.length):"animation"===e?h>0&&(n="animation",v=h,y=d.length):y=(n=(v=Math.max(f,h))>0?f>h?"transition":"animation":null)?"transition"===n?c.length:d.length:0,{type:n,timeout:v,propCount:y,hasTransform:"transition"===n&&uo.test(r[to+"Property"])}}function lo(t,e){for(;t.length1}function go(t,e){!0!==e.data.show&&ho(e)}var bo=function(t){var i,e,n={},r=t.modules,d=t.nodeOps;for(i=0;iw?A(t,o(n[S+1])?null:n[S+1].elm,n,_,S,r):_>S&&C(e,m,w)}(m,_,x,r,y):c(x)?(c(t.text)&&d.setTextContent(m,""),A(m,null,x,0,x.length-1,r)):c(_)?C(_,0,_.length-1):c(t.text)&&d.setTextContent(m,""):t.text!==e.text&&d.setTextContent(m,e.text),c(data)&&c(i=data.hook)&&c(i=i.postpatch)&&i(t,e)}}}function j(t,e,n){if(f(n)&&c(t.parent))t.parent.data.pendingInsert=e;else for(var i=0;i-1,option.selected!==c&&(option.selected=c);else if(B(So(option),r))return void(t.selectedIndex!==i&&(t.selectedIndex=i));o||(t.selectedIndex=-1)}}function Oo(t,e){return e.every((function(e){return!B(e,t)}))}function So(option){return"_value"in option?option._value:option.value}function Ao(t){t.target.composing=!0}function ko(t){t.target.composing&&(t.target.composing=!1,Co(t.target,"input"))}function Co(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function Eo(t){return!t.componentInstance||t.data&&t.data.transition?t:Eo(t.componentInstance._vnode)}var $o={model:_o,show:{bind:function(t,e,n){var r=e.value,o=(n=Eo(n)).data&&n.data.transition,c=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,ho(n,(function(){t.style.display=c}))):t.style.display=r?c:"none"},update:function(t,e,n){var r=e.value;!r!=!e.oldValue&&((n=Eo(n)).data&&n.data.transition?(n.data.show=!0,r?ho(n,(function(){t.style.display=t.__vOriginalDisplay})):vo(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none")},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}}},To={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function jo(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?jo(Qe(e.children)):t}function Io(t){var data={},e=t.$options;for(var n in e.propsData)data[n]=t[n];var r=e._parentListeners;for(var o in r)data[T(o)]=r[o];return data}function Po(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}var No=function(t){return t.tag||Ye(t)},Lo=function(t){return"show"===t.name},Ro={name:"transition",props:To,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(No)).length){0;var r=this.mode;0;var o=n[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return o;var c=jo(o);if(!c)return o;if(this._leaving)return Po(t,o);var f="__transition-"+this._uid+"-";c.key=null==c.key?c.isComment?f+"comment":f+c.tag:l(c.key)?0===String(c.key).indexOf(f)?c.key:f+c.key:c.key;var data=(c.data||(c.data={})).transition=Io(this),d=this._vnode,h=jo(d);if(c.data.directives&&c.data.directives.some(Lo)&&(c.data.show=!0),h&&h.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(c,h)&&!Ye(h)&&(!h.componentInstance||!h.componentInstance._vnode.isComment)){var v=h.data.transition=R({},data);if("out-in"===r)return this._leaving=!0,ve(v,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),Po(t,o);if("in-out"===r){if(Ye(c))return d;var y,m=function(){y()};ve(data,"afterEnter",m),ve(data,"enterCancelled",m),ve(v,"delayLeave",(function(t){y=t}))}}return o}}},Mo=R({tag:String,moveClass:String},To);function Do(t){t.elm._moveCb&&t.elm._moveCb(),t.elm._enterCb&&t.elm._enterCb()}function Fo(t){t.data.newPos=t.elm.getBoundingClientRect()}function Uo(t){var e=t.data.pos,n=t.data.newPos,r=e.left-n.left,o=e.top-n.top;if(r||o){t.data.moved=!0;var s=t.elm.style;s.transform=s.WebkitTransform="translate("+r+"px,"+o+"px)",s.transitionDuration="0s"}}delete Mo.mode;var Bo={Transition:Ro,TransitionGroup:{props:Mo,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=on(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",map=Object.create(null),n=this.prevChildren=this.children,r=this.$slots.default||[],o=this.children=[],c=Io(this),i=0;i-1?rr[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:rr[t]=/HTMLUnknownElement/.test(e.toString())},R(jn.options.directives,$o),R(jn.options.components,Bo),jn.prototype.__patch__=Z?bo:D,jn.prototype.$mount=function(t,e){return function(t,e,n){var r;return t.$el=e,t.$options.render||(t.$options.render=kt),cn(t,"beforeMount"),r=function(){t._update(t._render(),n)},new _n(t,r,D,{before:function(){t._isMounted&&!t._isDestroyed&&cn(t,"beforeUpdate")}},!0),n=!1,null==t.$vnode&&(t._isMounted=!0,cn(t,"mounted")),t}(this,t=t&&Z?function(t){if("string"==typeof t){var e=document.querySelector(t);return e||document.createElement("div")}return t}(t):void 0,e)},Z&&setTimeout((function(){W.devtools&&ht&&ht.emit("init",jn)}),0),e.a=jn}).call(this,n(39),n(157).setImmediate)},function(t,e,n){"use strict";function r(t,e,n,r,o,c,f){try{var l=t[c](f),d=l.value}catch(t){return void n(t)}l.done?e(d):Promise.resolve(d).then(r,o)}function o(t){return function(){var e=this,n=arguments;return new Promise((function(o,c){var f=t.apply(e,n);function l(t){r(f,o,c,l,d,"next",t)}function d(t){r(f,o,c,l,d,"throw",t)}l(void 0)}))}}n.d(e,"a",(function(){return o}))},function(t,e,n){var r=n(49)("wks"),o=n(41),c=n(4).Symbol,f="function"==typeof c;(t.exports=function(t){return r[t]||(r[t]=f&&c[t]||(f?c:o)("Symbol."+t))}).store=r},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e,n){var r=n(12);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,e,n){var r=n(4),o=n(16),c=n(17),f=n(13),l=n(35),d=function(t,e,source){var n,h,v,y,m=t&d.F,_=t&d.G,w=t&d.S,x=t&d.P,O=t&d.B,S=_?r:w?r[e]||(r[e]={}):(r[e]||{}).prototype,A=_?o:o[e]||(o[e]={}),k=A.prototype||(A.prototype={});for(n in _&&(source=e),source)v=((h=!m&&S&&void 0!==S[n])?S:source)[n],y=O&&h?l(v,r):x&&"function"==typeof v?l(Function.call,v):v,S&&f(S,n,v,t&d.U),A[n]!=v&&c(A,n,y),x&&k[n]!=v&&(k[n]=v)};r.core=o,d.F=1,d.G=2,d.S=4,d.P=8,d.B=16,d.W=32,d.U=64,d.R=128,t.exports=d},function(t,e,n){t.exports=!n(11)((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},function(t,e,n){"use strict";var r=n(104),o=Object.prototype.toString;function c(t){return"[object Array]"===o.call(t)}function f(t){return void 0===t}function l(t){return null!==t&&"object"==typeof t}function d(t){return"[object Function]"===o.call(t)}function h(t,e){if(null!=t)if("object"!=typeof t&&(t=[t]),c(t))for(var i=0,n=t.length;i=0;--i){var o=this.tryEntries[i],c=o.completion;if("root"===o.tryLoc)return r("end");if(o.tryLoc<=this.prev){var f=n.call(o,"catchLoc"),l=n.call(o,"finallyLoc");if(f&&l){if(this.prev=0;--i){var r=this.tryEntries[i];if(r.tryLoc<=this.prev&&n.call(r,"finallyLoc")&&this.prev=0;--i){var e=this.tryEntries[i];if(e.finallyLoc===t)return this.complete(e.completion,e.afterLoc),E(e),h}},catch:function(t){for(var i=this.tryEntries.length-1;i>=0;--i){var e=this.tryEntries[i];if(e.tryLoc===t){var n=e.completion;if("throw"===n.type){var r=n.arg;E(e)}return r}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,n){return this.delegate={iterator:T(t),resultName:e,nextLoc:n},"next"===this.method&&(this.arg=void 0),h}},t}(t.exports);try{regeneratorRuntime=r}catch(t){Function("r","regeneratorRuntime = r")(r)}},function(t,e){var n=t.exports={version:"2.6.11"};"number"==typeof __e&&(__e=n)},function(t,e,n){var r=n(9),o=n(42);t.exports=n(7)?function(object,t,e){return r.f(object,t,o(1,e))}:function(object,t,e){return object[t]=e,object}},function(t,e,n){var r=n(83),o=n(33);t.exports=function(t){return r(o(t))}},function(t,e,n){t.exports=n(171)},function(t,e,n){"use strict";var r=n(4),o=n(26),c=n(7),f=n(6),l=n(13),d=n(124).KEY,h=n(11),v=n(49),y=n(51),m=n(41),_=n(3),w=n(80),x=n(79),O=n(125),S=n(126),A=n(5),k=n(12),C=n(34),E=n(18),$=n(64),T=n(42),j=n(85),I=n(128),P=n(67),N=n(52),L=n(9),R=n(27),M=P.f,D=L.f,F=I.f,U=r.Symbol,B=r.JSON,z=B&&B.stringify,H=_("_hidden"),V=_("toPrimitive"),K={}.propertyIsEnumerable,W=v("symbol-registry"),G=v("symbols"),J=v("op-symbols"),X=Object.prototype,Y="function"==typeof U&&!!N.f,Q=r.QObject,Z=!Q||!Q.prototype||!Q.prototype.findChild,tt=c&&h((function(){return 7!=j(D({},"a",{get:function(){return D(this,"a",{value:7}).a}})).a}))?function(t,e,n){var r=M(X,e);r&&delete X[e],D(t,e,n),r&&t!==X&&D(X,e,r)}:D,et=function(t){var e=G[t]=j(U.prototype);return e._k=t,e},nt=Y&&"symbol"==typeof U.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof U},ot=function(t,e,n){return t===X&&ot(J,e,n),A(t),e=$(e,!0),A(n),o(G,e)?(n.enumerable?(o(t,H)&&t[H][e]&&(t[H][e]=!1),n=j(n,{enumerable:T(0,!1)})):(o(t,H)||D(t,H,T(1,{})),t[H][e]=!0),tt(t,e,n)):D(t,e,n)},it=function(t,e){A(t);for(var n,r=O(e=E(e)),i=0,o=r.length;o>i;)ot(t,n=r[i++],e[n]);return t},at=function(t){var e=K.call(this,t=$(t,!0));return!(this===X&&o(G,t)&&!o(J,t))&&(!(e||!o(this,t)||!o(G,t)||o(this,H)&&this[H][t])||e)},st=function(t,e){if(t=E(t),e=$(e,!0),t!==X||!o(G,e)||o(J,e)){var n=M(t,e);return!n||!o(G,e)||o(t,H)&&t[H][e]||(n.enumerable=!0),n}},ct=function(t){for(var e,n=F(E(t)),r=[],i=0;n.length>i;)o(G,e=n[i++])||e==H||e==d||r.push(e);return r},ut=function(t){for(var e,n=t===X,r=F(n?J:E(t)),c=[],i=0;r.length>i;)!o(G,e=r[i++])||n&&!o(X,e)||c.push(G[e]);return c};Y||(l((U=function(){if(this instanceof U)throw TypeError("Symbol is not a constructor!");var t=m(arguments.length>0?arguments[0]:void 0),e=function(n){this===X&&e.call(J,n),o(this,H)&&o(this[H],t)&&(this[H][t]=!1),tt(this,t,T(1,n))};return c&&Z&&tt(X,t,{configurable:!0,set:e}),et(t)}).prototype,"toString",(function(){return this._k})),P.f=st,L.f=ot,n(53).f=I.f=ct,n(44).f=at,N.f=ut,c&&!n(40)&&l(X,"propertyIsEnumerable",at,!0),w.f=function(t){return et(_(t))}),f(f.G+f.W+f.F*!Y,{Symbol:U});for(var ft="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),lt=0;ft.length>lt;)_(ft[lt++]);for(var pt=R(_.store),ht=0;pt.length>ht;)x(pt[ht++]);f(f.S+f.F*!Y,"Symbol",{for:function(t){return o(W,t+="")?W[t]:W[t]=U(t)},keyFor:function(t){if(!nt(t))throw TypeError(t+" is not a symbol!");for(var e in W)if(W[e]===t)return e},useSetter:function(){Z=!0},useSimple:function(){Z=!1}}),f(f.S+f.F*!Y,"Object",{create:function(t,e){return void 0===e?j(t):it(j(t),e)},defineProperty:ot,defineProperties:it,getOwnPropertyDescriptor:st,getOwnPropertyNames:ct,getOwnPropertySymbols:ut});var vt=h((function(){N.f(1)}));f(f.S+f.F*vt,"Object",{getOwnPropertySymbols:function(t){return N.f(C(t))}}),B&&f(f.S+f.F*(!Y||h((function(){var t=U();return"[null]"!=z([t])||"{}"!=z({a:t})||"{}"!=z(Object(t))}))),"JSON",{stringify:function(t){for(var e,n,r=[t],i=1;arguments.length>i;)r.push(arguments[i++]);if(n=e=r[1],(k(e)||void 0!==t)&&!nt(t))return S(e)||(e=function(t,e){if("function"==typeof n&&(e=n.call(this,t,e)),!nt(e))return e}),r[1]=e,z.apply(B,r)}}),U.prototype[V]||n(17)(U.prototype,V,U.prototype.valueOf),y(U,"Symbol"),y(Math,"Math",!0),y(r.JSON,"JSON",!0)},function(t,e,n){"use strict";n(129);var r=n(5),o=n(55),c=n(7),f=/./.toString,l=function(t){n(13)(RegExp.prototype,"toString",t,!0)};n(11)((function(){return"/a/b"!=f.call({source:"a",flags:"b"})}))?l((function(){var t=r(this);return"/".concat(t.source,"/","flags"in t?t.flags:!c&&t instanceof RegExp?o.call(t):void 0)})):"toString"!=f.name&&l((function(){return f.call(this)}))},function(t,e,n){var r=Date.prototype,o=r.toString,c=r.getTime;new Date(NaN)+""!="Invalid Date"&&n(13)(r,"toString",(function(){var t=c.call(this);return t==t?o.call(this):"Invalid Date"}))},function(t,e,n){for(var r=n(96),o=n(27),c=n(13),f=n(4),l=n(17),d=n(45),h=n(3),v=h("iterator"),y=h("toStringTag"),m=d.Array,_={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},w=o(_),i=0;i0?o(r(t),9007199254740991):0}},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var r=n(82),o=n(66);t.exports=Object.keys||function(t){return r(t,o)}},function(t,e,n){"use strict";function r(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i1?arguments[1]:void 0,O=void 0!==x,S=0,A=v(m);if(O&&(x=r(x,w>2?arguments[2]:void 0,2)),null==A||_==Array&&l(A))for(n=new _(e=d(m.length));e>S;S++)h(n,S,O?x(m[S],S):m[S]);else for(y=A.call(m),n=new _;!(o=y.next()).done;S++)h(n,S,O?f(y,x,[o.value,S],!0):o.value);return n.length=S,n}})},function(t,e,n){"use strict";var r=n(92)(!0);n(93)(String,"String",(function(t){this._t=String(t),this._i=0}),(function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=r(e,n),this._i+=t.length,{value:t,done:!1})}))},function(t,e){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,e,n){var r=n(33);t.exports=function(t){return Object(r(t))}},function(t,e,n){var r=n(50);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(a){return t.call(e,a)};case 2:return function(a,b){return t.call(e,a,b)};case 3:return function(a,b,n){return t.call(e,a,b,n)}}return function(){return t.apply(e,arguments)}}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e,n){"use strict";function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}n.d(e,"a",(function(){return r}))},,function(t,e){var g;g=function(){return this}();try{g=g||new Function("return this")()}catch(t){"object"==typeof window&&(g=window)}t.exports=g},function(t,e){t.exports=!1},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e){e.f={}.propertyIsEnumerable},function(t,e){t.exports={}},function(t,e,n){"use strict";function r(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function o(t,e){return e instanceof t||e&&(e.name===t.name||e._name===t._name)}function c(a,b){for(var t in b)a[t]=b[t];return a}var f={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(t,e){var n=e.props,r=e.children,o=e.parent,data=e.data;data.routerView=!0;for(var f=o.$createElement,d=n.name,h=o.$route,v=o._routerViewCache||(o._routerViewCache={}),y=0,m=!1;o&&o._routerRoot!==o;){var _=o.$vnode?o.$vnode.data:{};_.routerView&&y++,_.keepAlive&&o._directInactive&&o._inactive&&(m=!0),o=o.$parent}if(data.routerViewDepth=y,m){var w=v[d],x=w&&w.component;return x?(w.configProps&&l(x,data,w.route,w.configProps),f(x,data,r)):f()}var O=h.matched[y],component=O&&O.components[d];if(!O||!component)return v[d]=null,f();v[d]={component:component},data.registerRouteInstance=function(t,e){var n=O.instances[d];(e&&n!==t||!e&&n===t)&&(O.instances[d]=e)},(data.hook||(data.hook={})).prepatch=function(t,e){O.instances[d]=e.componentInstance},data.hook.init=function(t){t.data.keepAlive&&t.componentInstance&&t.componentInstance!==O.instances[d]&&(O.instances[d]=t.componentInstance)};var S=O.props&&O.props[d];return S&&(c(v[d],{route:h,configProps:S}),l(component,data,h,S)),f(component,data,r)}};function l(component,data,t,e){var n=data.props=function(t,e){switch(typeof e){case"undefined":return;case"object":return e;case"function":return e(t);case"boolean":return e?t.params:void 0;default:0}}(t,e);if(n){n=data.props=c({},n);var r=data.attrs=data.attrs||{};for(var o in n)component.props&&o in component.props||(r[o]=n[o],delete n[o])}}var d=/[!'()*]/g,h=function(t){return"%"+t.charCodeAt(0).toString(16)},v=/%2C/g,y=function(t){return encodeURIComponent(t).replace(d,h).replace(v,",")},m=decodeURIComponent;function _(t){var e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach((function(param){var t=param.replace(/\+/g," ").split("="),n=m(t.shift()),r=t.length>0?m(t.join("=")):null;void 0===e[n]?e[n]=r:Array.isArray(e[n])?e[n].push(r):e[n]=[e[n],r]})),e):e}function w(t){var e=t?Object.keys(t).map((function(e){var n=t[e];if(void 0===n)return"";if(null===n)return y(e);if(Array.isArray(n)){var r=[];return n.forEach((function(t){void 0!==t&&(null===t?r.push(y(e)):r.push(y(e)+"="+y(t)))})),r.join("&")}return y(e)+"="+y(n)})).filter((function(t){return t.length>0})).join("&"):null;return e?"?"+e:""}var x=/\/?$/;function O(t,e,n,r){var o=r&&r.options.stringifyQuery,c=e.query||{};try{c=S(c)}catch(t){}var f={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:c,params:e.params||{},fullPath:C(e,o),matched:t?k(t):[]};return n&&(f.redirectedFrom=C(n,o)),Object.freeze(f)}function S(t){if(Array.isArray(t))return t.map(S);if(t&&"object"==typeof t){var e={};for(var n in t)e[n]=S(t[n]);return e}return t}var A=O(null,{path:"/"});function k(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function C(t,e){var path=t.path,n=t.query;void 0===n&&(n={});var r=t.hash;return void 0===r&&(r=""),(path||"/")+(e||w)(n)+r}function E(a,b){return b===A?a===b:!!b&&(a.path&&b.path?a.path.replace(x,"")===b.path.replace(x,"")&&a.hash===b.hash&&$(a.query,b.query):!(!a.name||!b.name)&&(a.name===b.name&&a.hash===b.hash&&$(a.query,b.query)&&$(a.params,b.params)))}function $(a,b){if(void 0===a&&(a={}),void 0===b&&(b={}),!a||!b)return a===b;var t=Object.keys(a),e=Object.keys(b);return t.length===e.length&&t.every((function(t){var e=a[t],n=b[t];return"object"==typeof e&&"object"==typeof n?$(e,n):String(e)===String(n)}))}function T(t,base,e){var n=t.charAt(0);if("/"===n)return t;if("?"===n||"#"===n)return base+t;var r=base.split("/");e&&r[r.length-1]||r.pop();for(var o=t.replace(/^\//,"").split("/"),i=0;i=0&&(t=path.slice(n),path=path.slice(0,n));var r=path.indexOf("?");return r>=0&&(e=path.slice(r+1),path=path.slice(0,r)),{path:path,query:e,hash:t}}(o.path||""),v=e&&e.path||"/",path=h.path?T(h.path,v,n||o.append):v,y=function(t,e,n){void 0===e&&(e={});var r,o=n||_;try{r=o(t||"")}catch(t){r={}}for(var c in e)r[c]=e[c];return r}(h.query,o.query,r&&r.options.parseQuery),m=o.hash||h.hash;return m&&"#"!==m.charAt(0)&&(m="#"+m),{_normalized:!0,path:path,query:y,hash:m}}var Q,Z=function(){},tt={name:"RouterLink",props:{to:{type:[String,Object],required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,event:{type:[String,Array],default:"click"}},render:function(t){var e=this,n=this.$router,r=this.$route,o=n.resolve(this.to,r,this.append),f=o.location,l=o.route,d=o.href,h={},v=n.options.linkActiveClass,y=n.options.linkExactActiveClass,m=null==v?"router-link-active":v,_=null==y?"router-link-exact-active":y,w=null==this.activeClass?m:this.activeClass,S=null==this.exactActiveClass?_:this.exactActiveClass,A=l.redirectedFrom?O(null,Y(l.redirectedFrom),null,n):l;h[S]=E(r,A),h[w]=this.exact?h[S]:function(t,e){return 0===t.path.replace(x,"/").indexOf(e.path.replace(x,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var n in e)if(!(n in t))return!1;return!0}(t.query,e.query)}(r,A);var k=function(t){et(t)&&(e.replace?n.replace(f,Z):n.push(f,Z))},C={click:et};Array.isArray(this.event)?this.event.forEach((function(t){C[t]=k})):C[this.event]=k;var data={class:h},$=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:d,route:l,navigate:k,isActive:h[w],isExactActive:h[S]});if($){if(1===$.length)return $[0];if($.length>1||!$.length)return 0===$.length?t():t("span",{},$)}if("a"===this.tag)data.on=C,data.attrs={href:d};else{var a=function t(e){var n;if(e)for(var i=0;i-1&&(l.params[m]=n.params[m]);return l.path=X(v.path,l.params),d(v,l,f)}if(l.path){l.params={};for(var i=0;i=t.length?n():t[o]?e(t[o],(function(){r(o+1)})):r(o+1)};r(0)}function Et(t){return function(e,n,o){var c=!1,f=0,l=null;$t(t,(function(t,e,n,d){if("function"==typeof t&&void 0===t.cid){c=!0,f++;var h,v=It((function(e){var r;((r=e).__esModule||jt&&"Module"===r[Symbol.toStringTag])&&(e=e.default),t.resolved="function"==typeof e?e:Q.extend(e),n.components[d]=e,--f<=0&&o()})),y=It((function(t){var e="Failed to resolve async component "+d+": "+t;l||(l=r(t)?t:new Error(e),o(l))}));try{h=t(v,y)}catch(t){y(t)}if(h)if("function"==typeof h.then)h.then(v,y);else{var m=h.component;m&&"function"==typeof m.then&&m.then(v,y)}}})),c||o()}}function $t(t,e){return Tt(t.map((function(t){return Object.keys(t.components).map((function(n){return e(t.components[n],t.instances[n],t,n)}))})))}function Tt(t){return Array.prototype.concat.apply([],t)}var jt="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function It(t){var e=!1;return function(){for(var n=[],r=arguments.length;r--;)n[r]=arguments[r];if(!e)return e=!0,t.apply(this,n)}}var Pt=function(t){function e(e){t.call(this),this.name=this._name="NavigationDuplicated",this.message='Navigating to current location ("'+e.fullPath+'") is not allowed',Object.defineProperty(this,"stack",{value:(new t).stack,writable:!0,configurable:!0})}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e}(Error);Pt._name="NavigationDuplicated";var Nt=function(t,base){this.router=t,this.base=function(base){if(!base)if(nt){var t=document.querySelector("base");base=(base=t&&t.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else base="/";"/"!==base.charAt(0)&&(base="/"+base);return base.replace(/\/$/,"")}(base),this.current=A,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function Lt(t,e,n,r){var o=$t(t,(function(t,r,o,c){var f=function(t,e){"function"!=typeof t&&(t=Q.extend(t));return t.options[e]}(t,e);if(f)return Array.isArray(f)?f.map((function(t){return n(t,r,o,c)})):n(f,r,o,c)}));return Tt(r?o.reverse():o)}function Rt(t,e){if(e)return function(){return t.apply(e,arguments)}}Nt.prototype.listen=function(t){this.cb=t},Nt.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},Nt.prototype.onError=function(t){this.errorCbs.push(t)},Nt.prototype.transitionTo=function(t,e,n){var r=this,o=this.router.match(t,this.current);this.confirmTransition(o,(function(){r.updateRoute(o),e&&e(o),r.ensureURL(),r.ready||(r.ready=!0,r.readyCbs.forEach((function(t){t(o)})))}),(function(t){n&&n(t),t&&!r.ready&&(r.ready=!0,r.readyErrorCbs.forEach((function(e){e(t)})))}))},Nt.prototype.confirmTransition=function(t,e,n){var c=this,f=this.current,l=function(t){!o(Pt,t)&&r(t)&&(c.errorCbs.length?c.errorCbs.forEach((function(e){e(t)})):console.error(t)),n&&n(t)};if(E(t,f)&&t.matched.length===f.matched.length)return this.ensureURL(),l(new Pt(t));var d=function(t,e){var i,n=Math.max(t.length,e.length);for(i=0;i-1?decodeURI(t.slice(0,r))+t.slice(r):decodeURI(t)}else t=decodeURI(t.slice(0,n))+t.slice(n);return t}function zt(path){var t=window.location.href,i=t.indexOf("#");return(i>=0?t.slice(0,i):t)+"#"+path}function qt(path){St?At(zt(path)):window.location.hash=path}function Ht(path){St?kt(zt(path)):window.location.replace(zt(path))}var Vt=function(t){function e(e,base){t.call(this,e,base),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index+1).concat(t),r.index++,e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index).concat(t),e&&e(t)}),n)},e.prototype.go=function(t){var e=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){e.index=n,e.updateRoute(r)}),(function(t){o(Pt,t)&&(e.index=n)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(Nt),Kt=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=at(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!St&&!1!==t.fallback,this.fallback&&(e="hash"),nt||(e="abstract"),this.mode=e,e){case"history":this.history=new Mt(this,t.base);break;case"hash":this.history=new Ft(this,t.base,this.fallback);break;case"abstract":this.history=new Vt(this,t.base);break;default:0}},Wt={currentRoute:{configurable:!0}};function Gt(t,e){return t.push(e),function(){var i=t.indexOf(e);i>-1&&t.splice(i,1)}}Kt.prototype.match=function(t,e,n){return this.matcher.match(t,e,n)},Wt.currentRoute.get=function(){return this.history&&this.history.current},Kt.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",(function(){var n=e.apps.indexOf(t);n>-1&&e.apps.splice(n,1),e.app===t&&(e.app=e.apps[0]||null)})),!this.app){this.app=t;var n=this.history;if(n instanceof Mt)n.transitionTo(n.getCurrentLocation());else if(n instanceof Ft){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},Kt.prototype.beforeEach=function(t){return Gt(this.beforeHooks,t)},Kt.prototype.beforeResolve=function(t){return Gt(this.resolveHooks,t)},Kt.prototype.afterEach=function(t){return Gt(this.afterHooks,t)},Kt.prototype.onReady=function(t,e){this.history.onReady(t,e)},Kt.prototype.onError=function(t){this.history.onError(t)},Kt.prototype.push=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((function(e,n){r.history.push(t,e,n)}));this.history.push(t,e,n)},Kt.prototype.replace=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((function(e,n){r.history.replace(t,e,n)}));this.history.replace(t,e,n)},Kt.prototype.go=function(t){this.history.go(t)},Kt.prototype.back=function(){this.go(-1)},Kt.prototype.forward=function(){this.go(1)},Kt.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},Kt.prototype.resolve=function(t,e,n){var r=Y(t,e=e||this.history.current,n,this),o=this.match(r,e),c=o.redirectedFrom||o.fullPath;return{location:r,route:o,href:function(base,t,e){var path="hash"===e?"#"+t:t;return base?j(base+"/"+path):path}(this.history.base,c,this.mode),normalizedTo:r,resolved:o}},Kt.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==A&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(Kt.prototype,Wt),Kt.install=function t(e){if(!t.installed||Q!==e){t.installed=!0,Q=e;var n=function(t){return void 0!==t},r=function(t,e){var i=t.$options._parentVnode;n(i)&&n(i=i.data)&&n(i=i.registerRouteInstance)&&i(t,e)};e.mixin({beforeCreate:function(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,r(this,this)},destroyed:function(){r(this)}}),Object.defineProperty(e.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get:function(){return this._routerRoot._route}}),e.component("RouterView",f),e.component("RouterLink",tt);var o=e.config.optionMergeStrategies;o.beforeRouteEnter=o.beforeRouteLeave=o.beforeRouteUpdate=o.created}},Kt.version="3.1.6",nt&&window.Vue&&window.Vue.use(Kt),e.a=Kt},,,function(t,e,n){var r=n(16),o=n(4),c=o["__core-js_shared__"]||(o["__core-js_shared__"]={});(t.exports=function(t,e){return c[t]||(c[t]=void 0!==e?e:{})})("versions",[]).push({version:r.version,mode:n(40)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,n){var r=n(9).f,o=n(26),c=n(3)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,c)&&r(t,c,{configurable:!0,value:e})}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){var r=n(82),o=n(66).concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return r(t,o)}},function(t,e,n){var r=n(36),o=n(3)("toStringTag"),c="Arguments"==r(function(){return arguments}());t.exports=function(t){var e,n,f;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=function(t,e){try{return t[e]}catch(t){}}(e=Object(t),o))?n:c?r(e):"Object"==(f=r(e))&&"function"==typeof e.callee?"Arguments":f}},function(t,e,n){"use strict";var r=n(5);t.exports=function(){var t=r(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},function(t,e,n){"use strict";var r=n(54),o=RegExp.prototype.exec;t.exports=function(t,e){var n=t.exec;if("function"==typeof n){var c=n.call(t,e);if("object"!=typeof c)throw new TypeError("RegExp exec method returned something other than an Object or null");return c}if("RegExp"!==r(t))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(t,e)}},function(t,e,n){"use strict";n(133);var r=n(13),o=n(17),c=n(11),f=n(33),l=n(3),d=n(69),h=l("species"),v=!c((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),y=function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();t.exports=function(t,e,n){var m=l(t),_=!c((function(){var e={};return e[m]=function(){return 7},7!=""[t](e)})),w=_?!c((function(){var e=!1,n=/a/;return n.exec=function(){return e=!0,null},"split"===t&&(n.constructor={},n.constructor[h]=function(){return n}),n[m](""),!e})):void 0;if(!_||!w||"replace"===t&&!v||"split"===t&&!y){var x=/./[m],O=n(f,m,""[t],(function(t,e,n,r,o){return e.exec===d?_&&!o?{done:!0,value:x.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}})),S=O[0],A=O[1];r(String.prototype,t,S),o(RegExp.prototype,m,2==e?function(t,e){return A.call(t,this,e)}:function(t){return A.call(t,this)})}}},function(t,e,n){"use strict";function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}n.d(e,"a",(function(){return r}))},,,,,function(t,e,n){var r=n(12),o=n(4).document,c=r(o)&&r(o.createElement);t.exports=function(t){return c?o.createElement(t):{}}},function(t,e,n){var r=n(12);t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){var r=n(49)("keys"),o=n(41);t.exports=function(t){return r[t]||(r[t]=o(t))}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,n){var r=n(44),o=n(42),c=n(18),f=n(64),l=n(26),d=n(81),h=Object.getOwnPropertyDescriptor;e.f=n(7)?h:function(t,e){if(t=c(t),e=f(e,!0),d)try{return h(t,e)}catch(t){}if(l(t,e))return o(!r.f.call(t,e),t[e])}},function(t,e,n){"use strict";var r=n(92)(!0);t.exports=function(t,e,n){return e+(n?r(t,e).length:1)}},function(t,e,n){"use strict";var r,o,c=n(55),f=RegExp.prototype.exec,l=String.prototype.replace,d=f,h=(r=/a/,o=/b*/g,f.call(r,"a"),f.call(o,"a"),0!==r.lastIndex||0!==o.lastIndex),v=void 0!==/()??/.exec("")[1];(h||v)&&(d=function(t){var e,n,r,i,o=this;return v&&(n=new RegExp("^"+o.source+"$(?!\\s)",c.call(o))),h&&(e=o.lastIndex),r=f.call(o,t),h&&r&&(o.lastIndex=o.global?r.index+r[0].length:e),v&&r&&r.length>1&&l.call(r[0],n,(function(){for(i=1;i1?arguments[1]:void 0)}}),n(78)("includes")},function(t,e,n){"use strict";var r=n(6),o=n(94);r(r.P+r.F*n(95)("includes"),"String",{includes:function(t){return!!~o(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,e,n){var r=n(12),o=n(36),c=n(3)("match");t.exports=function(t){var e;return r(t)&&(void 0!==(e=t[c])?!!e:"RegExp"==o(t))}},function(t,e,n){var r=n(34),o=n(27);n(135)("keys",(function(){return function(t){return o(r(t))}}))},function(t,e,n){var r=n(5),o=n(50),c=n(3)("species");t.exports=function(t,e){var n,f=r(t).constructor;return void 0===f||null==(n=r(f)[c])?e:o(n)}},,,,function(t,e,n){var r=n(3)("unscopables"),o=Array.prototype;null==o[r]&&n(17)(o,r,{}),t.exports=function(t){o[r][t]=!0}},function(t,e,n){var r=n(4),o=n(16),c=n(40),f=n(80),l=n(9).f;t.exports=function(t){var e=o.Symbol||(o.Symbol=c?{}:r.Symbol||{});"_"==t.charAt(0)||t in e||l(e,t,{value:f.f(t)})}},function(t,e,n){e.f=n(3)},function(t,e,n){t.exports=!n(7)&&!n(11)((function(){return 7!=Object.defineProperty(n(63)("div"),"a",{get:function(){return 7}}).a}))},function(t,e,n){var r=n(26),o=n(18),c=n(84)(!1),f=n(65)("IE_PROTO");t.exports=function(object,t){var e,n=o(object),i=0,l=[];for(e in n)e!=f&&r(n,e)&&l.push(e);for(;t.length>i;)r(n,e=t[i++])&&(~c(l,e)||l.push(e));return l}},function(t,e,n){var r=n(36);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,e,n){var r=n(18),o=n(25),c=n(119);t.exports=function(t){return function(e,n,f){var l,d=r(e),h=o(d.length),v=c(f,h);if(t&&n!=n){for(;h>v;)if((l=d[v++])!=l)return!0}else for(;h>v;v++)if((t||v in d)&&d[v]===n)return t||v||0;return!t&&-1}}},function(t,e,n){var r=n(5),o=n(127),c=n(66),f=n(65)("IE_PROTO"),l=function(){},d=function(){var t,iframe=n(63)("iframe"),i=c.length;for(iframe.style.display="none",n(86).appendChild(iframe),iframe.src="javascript:",(t=iframe.contentWindow.document).open(),t.write(" + Page Redirection + + + + If you are not redirected automatically, follow this link to GINO docs. + + + diff --git a/components/README.md b/components/README.md deleted file mode 100644 index a079f10..0000000 --- a/components/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# COMPONENTS - -**This directory is not required, you can delete it if you don't want to use it.** - -The components directory contains your Vue.js Components. - -_Nuxt.js doesn't supercharge these components._ diff --git a/components/architecture.vue b/components/architecture.vue deleted file mode 100644 index b8cfb67..0000000 --- a/components/architecture.vue +++ /dev/null @@ -1,164 +0,0 @@ - - - diff --git a/components/highlights.vue b/components/highlights.vue deleted file mode 100644 index 5d1ec30..0000000 --- a/components/highlights.vue +++ /dev/null @@ -1,132 +0,0 @@ - - - diff --git a/components/landing.vue b/components/landing.vue deleted file mode 100644 index 410129c..0000000 --- a/components/landing.vue +++ /dev/null @@ -1,187 +0,0 @@ - - - - - diff --git a/components/quotes.vue b/components/quotes.vue deleted file mode 100644 index 804f123..0000000 --- a/components/quotes.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - diff --git a/components/roadmap.vue b/components/roadmap.vue deleted file mode 100644 index 71de770..0000000 --- a/components/roadmap.vue +++ /dev/null @@ -1,275 +0,0 @@ - - - - - diff --git a/components/showcases.vue b/components/showcases.vue deleted file mode 100644 index f0eab5a..0000000 --- a/components/showcases.vue +++ /dev/null @@ -1,229 +0,0 @@ - - - - - diff --git a/credits/index.html b/credits/index.html new file mode 100644 index 0000000..29ce8ab --- /dev/null +++ b/credits/index.html @@ -0,0 +1,73 @@ + + + + python-gino.org + + +

YOUR NAME HERE

+ Open source loves contribution! Your support is very much appreciated and we'll + add your names here for: +

  • Accepted Pull Requests
  • Accepted Translations
  • Sponsorship

We also appreciate:

  • Using, sharing and adding stars⭐!
  • Sharing your story
  • Reporting a bug/issue, or requesting for new features
  • Asking a question

CORE TEAM

  • Fantix King

    + Senior Software Architect
    + Center for Translational Data Science
    + The University of Chicago +

    + Chicago, IL +

    GitHub Twitter

  • Tony Wang

    + Software Engineer at Grab +

    + Singapore +

    GitHub Twitter

ACKNOWLEDGEMENTS

DECENTFOX

  • + Special thanks to my wife Daisy and her outsourcing company DecentFoX Studio, for + offering me the opportunity to build this project in 2018. They are open + for global software project outsourcing on Python, Web, iOS and Android + development. +

    + Much thanks to Mico Liu from DecentFox for + designing this awesome website, and Aobo Shi for + maintaining the extensions! +

CONTRIBUTORS

  • Neal Wang

    qdzzyb2015@gamil.com

  • Binghan Li

    lbhsot@163.com

  • Vladimir Goncharov

    dev.zelta@gmail.com

  • Kinware

    oss@kinware.com

  • Kentoseth

    kentoseth@devcroo.com

  • Ádám Barancsuk

    adam.barancsuk@gmail.com

  • Sergey Kovalev

    s_kovalev@wargaming.net

  • jonahfang

  • Yurii Shtrikker

    nbb.unitto@gmail.com

  • Nicolas Crocfer

    ncrocfer@gmail.com

  • Denys Badzo

    badzyo360@gmail.com

  • Pavol Vargovcik

    pavol.vargovcik@gmail.com

  • Mykyta Holubakha

    hilobakho@gmail.com

  • Jekel

    jekel@psyspace.ru

  • Martin Zaťko

    martin.zatko@kiwi.com

  • Pascal van Kooten

    kootenpv@gmail.com

  • Michał Dziewulski

    michal@dziewulski.pl

  • Simeon J Morgan

    smorgan@digitalfeed.net

  • Julio Lacerda

    julio.castro.lacerda@gmail.com

  • qulaz

    qulazwork@gmail.com

  • Jim O'Brien

    jim@greenfield.fm

  • Ilaï Deutel

  • Roald Storm

    roaldstorm@gmail.com

  • Tiago Requeijo

    tiago.requeijo.dev@gmail.com

  • Olexiy

    alosha969@gmail.com

  • Roman Averchenkov

    aragentum@gmail.com

OTHER PROJECTS

GINO is built on top of these awesome projects:

Projects using GINO (please + edit on GitHub to add your projects here):

GINO was inspired by these projects:

Other similar Python asyncio ORM projects:

+ + diff --git a/docs/en/1.0/.buildinfo b/docs/en/1.0/.buildinfo new file mode 100644 index 0000000..4ffc746 --- /dev/null +++ b/docs/en/1.0/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 3affebab71f327816f7f6eb1d54b9bd1 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/en/1.0/_images/263px-Minimum-Tonne.svg.png b/docs/en/1.0/_images/263px-Minimum-Tonne.svg.png new file mode 100644 index 0000000..f431e31 Binary files /dev/null and b/docs/en/1.0/_images/263px-Minimum-Tonne.svg.png differ diff --git a/docs/en/1.0/_images/archlinux.webp b/docs/en/1.0/_images/archlinux.webp new file mode 100644 index 0000000..3e358ef Binary files /dev/null and b/docs/en/1.0/_images/archlinux.webp differ diff --git a/docs/en/1.0/_images/community.svg b/docs/en/1.0/_images/community.svg new file mode 100644 index 0000000..dae6507 --- /dev/null +++ b/docs/en/1.0/_images/community.svg @@ -0,0 +1,26 @@ + + + + co-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/connection.png b/docs/en/1.0/_images/connection.png new file mode 100644 index 0000000..e54598d Binary files /dev/null and b/docs/en/1.0/_images/connection.png differ diff --git a/docs/en/1.0/_images/docs.webp b/docs/en/1.0/_images/docs.webp new file mode 100644 index 0000000..20259b1 Binary files /dev/null and b/docs/en/1.0/_images/docs.webp differ diff --git a/docs/en/1.0/_images/engine.png b/docs/en/1.0/_images/engine.png new file mode 100644 index 0000000..6b64561 Binary files /dev/null and b/docs/en/1.0/_images/engine.png differ diff --git a/docs/en/1.0/_images/exchangeratesapi.webp b/docs/en/1.0/_images/exchangeratesapi.webp new file mode 100644 index 0000000..dc2657a Binary files /dev/null and b/docs/en/1.0/_images/exchangeratesapi.webp differ diff --git a/docs/en/1.0/_images/explanation.svg b/docs/en/1.0/_images/explanation.svg new file mode 100644 index 0000000..ef775d1 --- /dev/null +++ b/docs/en/1.0/_images/explanation.svg @@ -0,0 +1,19 @@ + + + + ex-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-alembic.svg b/docs/en/1.0/_images/gino-fastapi-alembic.svg new file mode 100644 index 0000000..0cfc4b1 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-alembic.svg @@ -0,0 +1,3 @@ + + +
gino-fastapi-demo
gino-...
alembic.ini
ale...
migrations
migra...
env.py
env...
versions
versi...
32c0feba61ea_add_users_table.py
32c...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-config.svg b/docs/en/1.0/_images/gino-fastapi-config.svg new file mode 100644 index 0000000..673d084 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-config.svg @@ -0,0 +1,3 @@ + + +
src
src
gino_fastapi_demo
gino_...
config.py
con...
gino-fastapi-demo
gino-...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-env.svg b/docs/en/1.0/_images/gino-fastapi-env.svg new file mode 100644 index 0000000..4ec9e4e --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-env.svg @@ -0,0 +1,3 @@ + + +
.env
.env
gino-fastapi-demo
gino-...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-layout.svg b/docs/en/1.0/_images/gino-fastapi-layout.svg new file mode 100644 index 0000000..07f4701 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-layout.svg @@ -0,0 +1,3 @@ + + +
gino-fastapi-demo
gino-...
src
src
gino_fastapi_demo
gino_...
.env
.env
models
models
__init__.py
__i...
users.py
use...
views
views
__init__.py
__i...
users.py
use...
__init__.py
__i...
asgi.py
asg...
config.py
con...
main.py
mai...
tests
tests
conftest.py
con...
test_users.py
tes...
migrations
migra...
pyproject.toml
pyp...
poetry.lock
poe...
The project root directory.

Alembic data directory.

Database migration revisions directory.

One of the revisions.

Alembic Python environment.

Application source code container.

Project root python package.

Database models and GINO instance.

GINO instance (SQLAlchemy Metadata).

Models for users.

API implementation.



User-related APIs.



ASGI entry point.

Starlette-style application configuration.

Application initialization.

Testing code.

pytest fixtures.

User-related tests.

Local config, not in Git control.

Alembic entry config.

Production Docker image.

Poetry-frozen dependency versions.

Project and dependency definition.
The project root directory....
alembic.ini
ale...
Dockerfile
Doc...
env.py
env...
versions
versi...
32c0feba61ea....py
32c...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-models-users.svg b/docs/en/1.0/_images/gino-fastapi-models-users.svg new file mode 100644 index 0000000..886d581 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-models-users.svg @@ -0,0 +1,3 @@ + + +
src
src
gino_fastapi_demo
gino_...
models
models
__init__.py
__i...
users.py
use...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-models.svg b/docs/en/1.0/_images/gino-fastapi-models.svg new file mode 100644 index 0000000..4dc983c --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-models.svg @@ -0,0 +1,3 @@ + + +
src
src
gino_fastapi_demo
gino_...
models
models
__init__.py
__i...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-poetry.svg b/docs/en/1.0/_images/gino-fastapi-poetry.svg new file mode 100644 index 0000000..e0da8c1 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-poetry.svg @@ -0,0 +1,3 @@ + + +
gino-fastapi-demo
gino-...
pyproject.toml
pyp...
poetry.lock
poe...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-src.svg b/docs/en/1.0/_images/gino-fastapi-src.svg new file mode 100644 index 0000000..7310163 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-src.svg @@ -0,0 +1,3 @@ + + +
gino-fastapi-demo
gino-...
src
src
gino_fastapi_demo
gino_...
__init__.py
__i...
asgi.py
asg...
main.py
mai...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-tests.svg b/docs/en/1.0/_images/gino-fastapi-tests.svg new file mode 100644 index 0000000..53673d8 --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-tests.svg @@ -0,0 +1,3 @@ + + +
gino-fastapi-demo
gino-...
tests
tests
test_users.py
tes...
conftest.py
con...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi-views.svg b/docs/en/1.0/_images/gino-fastapi-views.svg new file mode 100644 index 0000000..7321a7a --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi-views.svg @@ -0,0 +1,3 @@ + + +
src
src
gino_fastapi_demo
gino_...
views
views
__init__.py
__i...
users.py
use...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/gino-fastapi.svg b/docs/en/1.0/_images/gino-fastapi.svg new file mode 100644 index 0000000..9d365ba --- /dev/null +++ b/docs/en/1.0/_images/gino-fastapi.svg @@ -0,0 +1,3 @@ + + +
Gunicorn
Gunicorn
Uvicorn
Uvicorn
Starlette
Starlette
FastAPI
FastAPI
API Implementation
API Implementation
GINO Models
GINO Models
GINO with Starlette
GINO with Starlette
SQLAlchemy core
SQLAlchemy core
asyncpg
asyncpg
Alembic
Alembic
psycopg2
psycopg2
PostgreSQL
PostgreSQL
Application Server
Application Server
ASGI Middleware
ASGI Middleware
Web Framework
Web Framework
Tutorial Code
Tutorial Code
GINO
GINO
Database Library
Database Library
HTTP API
HTTP API
DB Migration CLI
DB Migration CLI
Legend
Legend
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/1.0/_images/github.svg b/docs/en/1.0/_images/github.svg new file mode 100644 index 0000000..cb2484b --- /dev/null +++ b/docs/en/1.0/_images/github.svg @@ -0,0 +1,27 @@ + + + + sourcecode + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/happy-hacking.png b/docs/en/1.0/_images/happy-hacking.png new file mode 100644 index 0000000..701e7bd Binary files /dev/null and b/docs/en/1.0/_images/happy-hacking.png differ diff --git a/docs/en/1.0/_images/how-to.svg b/docs/en/1.0/_images/how-to.svg new file mode 100644 index 0000000..7461024 --- /dev/null +++ b/docs/en/1.0/_images/how-to.svg @@ -0,0 +1,22 @@ + + + + guide + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/open-source.svg b/docs/en/1.0/_images/open-source.svg new file mode 100644 index 0000000..0946f0a --- /dev/null +++ b/docs/en/1.0/_images/open-source.svg @@ -0,0 +1,18 @@ + + + + bsd + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/python-gino.webp b/docs/en/1.0/_images/python-gino.webp new file mode 100644 index 0000000..f6c11ac Binary files /dev/null and b/docs/en/1.0/_images/python-gino.webp differ diff --git a/docs/en/1.0/_images/python.svg b/docs/en/1.0/_images/python.svg new file mode 100644 index 0000000..f62b29b --- /dev/null +++ b/docs/en/1.0/_images/python.svg @@ -0,0 +1,22 @@ + + + + download + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/reference.svg b/docs/en/1.0/_images/reference.svg new file mode 100644 index 0000000..0c5f656 --- /dev/null +++ b/docs/en/1.0/_images/reference.svg @@ -0,0 +1,19 @@ + + + + re-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/tutorials.svg b/docs/en/1.0/_images/tutorials.svg new file mode 100644 index 0000000..2eb2111 --- /dev/null +++ b/docs/en/1.0/_images/tutorials.svg @@ -0,0 +1,26 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_images/why_coroutine.png b/docs/en/1.0/_images/why_coroutine.png new file mode 100644 index 0000000..99c743a Binary files /dev/null and b/docs/en/1.0/_images/why_coroutine.png differ diff --git a/docs/en/1.0/_images/why_multicore.png b/docs/en/1.0/_images/why_multicore.png new file mode 100644 index 0000000..c209a9f Binary files /dev/null and b/docs/en/1.0/_images/why_multicore.png differ diff --git a/docs/en/1.0/_images/why_multithreading.png b/docs/en/1.0/_images/why_multithreading.png new file mode 100644 index 0000000..e0e4321 Binary files /dev/null and b/docs/en/1.0/_images/why_multithreading.png differ diff --git a/docs/en/1.0/_images/why_single_task.png b/docs/en/1.0/_images/why_single_task.png new file mode 100644 index 0000000..54fab08 Binary files /dev/null and b/docs/en/1.0/_images/why_single_task.png differ diff --git a/docs/en/1.0/_images/why_throughput.png b/docs/en/1.0/_images/why_throughput.png new file mode 100644 index 0000000..388f533 Binary files /dev/null and b/docs/en/1.0/_images/why_throughput.png differ diff --git a/docs/en/1.0/_sources/explanation.rst.txt b/docs/en/1.0/_sources/explanation.rst.txt new file mode 100644 index 0000000..6d91357 --- /dev/null +++ b/docs/en/1.0/_sources/explanation.rst.txt @@ -0,0 +1,7 @@ +Explanation +=========== + +.. toctree:: + :glob: + + explanation/* diff --git a/docs/en/1.0/_sources/explanation/async.rst.txt b/docs/en/1.0/_sources/explanation/async.rst.txt new file mode 100644 index 0000000..a857170 --- /dev/null +++ b/docs/en/1.0/_sources/explanation/async.rst.txt @@ -0,0 +1,214 @@ +Asynchronous Programming 101 +============================ + + +The Story +--------- + +Let's say we want to build a search engine. We'll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this: + +.. image:: ../images/why_single_task.png + :align: center + +We have lots of web pages to index, so we simply handle them one by one: + +.. image:: ../images/why_throughput.png + :align: center + +Let's assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores: + +.. image:: ../images/why_multicore.png + :align: center + +This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading: + +.. image:: ../images/why_multithreading.png + :align: center + +Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What's wrong with multi-threading? From the diagram we can see: + +* There are yellow bars taking up extra time. +* The green bars can still overlap with any bar in the other thread, but +* non-green bars cannot overlap with non-green bars in the other thread. + +The yellow bars are time taken by `context switches +`_, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let's assume a world without +`Hyper-threading `_ or similar), +so in order to run several threads concurrently the CPU must `split its +time `_ into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point. + +Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it's waiting for the HTTP response (I/O). That's how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won't be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That's also why this is called concurrency instead of parallelism. + +As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous `C10k +problem `_, usually solved by +asynchronous I/O: + +.. image:: ../images/why_coroutine.png + :align: center + +.. note:: + + Asynchronous I/O and coroutines are two different things, but they usually + go together. Here we will stick with coroutines for simplicity. + +Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem. + + +Cooperative multitasking +------------------------ + +So what is a coroutine? + +In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread. + +Threads are scheduled by the operating system using an approach called `preemptive +multitasking `_. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn't +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this: + +.. code-block:: none + + Thread 1: I wanna run! + OS: Okay, here you go... + Thread 2: I wanna run! + OS: Urh, alright one sec ... Thread 1, hold on for a while! + Thread 1: Well I'm not done yet, but you are the boss. + OS: It won't be long. Thread 2 it's your turn now. + Thread 2: Yay! (&%#$@..+*&#) + Thread 1: Can I run now? + OS: Just a moment please ... Thread 2, give it a break! + Thread 2: Alright ... but I really need the CPU. + OS: You'll have it later. Thread 1, hurry up! + +In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don't - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +`cooperative multitasking +`_. It's like this: + +.. code-block:: none + + Coroutine 1: Let me know when event A arrives. I'm done here before that. + Event manager: Okay. What about you, coroutine 2? + Coroutine 2: Um I've got nothing to do here before event B. + Event manager: Cool, I'll be watching. + Event manager: (after a while) Hey coroutine 1, event A is here! + Coroutine 1: Awesome! Let me see ... looks good, but I need event C now. + Event manager: Very well. Seems event B arrived just now, coroutine 2? + Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done. + Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while. + (silence) + Event manager: Damn, event C timed out! + Coroutine 1: Arrrrh gotta kill myself with an exception :S + Event manager: Up to you :/ + +For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That's why in the last diagram, +the red bars are not interleaved like threads. + +.. tip:: + + In Python and asyncio, ``async def`` declares coroutines, ``await`` yields + control to event loop (event manager). + + +Pros and cons +------------- + +Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput. + +With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code. + +However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple ``recv()`` operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to ``recv()``, repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop_ this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O. + +Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, ``sleep(1)`` +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between ``await`` finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind. + +Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It's not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure. + + +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/en/1.0/_sources/explanation/engine.rst.txt b/docs/en/1.0/_sources/explanation/engine.rst.txt new file mode 100644 index 0000000..ebd59ed --- /dev/null +++ b/docs/en/1.0/_sources/explanation/engine.rst.txt @@ -0,0 +1,521 @@ +===================== +Engine and Connection +===================== + +:class:`~gino.engine.GinoEngine` is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together: + +.. image:: ../images/engine.png + :align: center + +Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users. + +During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use. + +.. note:: + + In SQLAlchemy, database drivers are supposed to follow the DB-API standard, + which does not usually provide a pool implementation. Therefore, SQLAlchemy + has its own pool implementation, created directly in engine. This is where + this diagram doesn't fit SQLAlchemy. + +The pool creates raw connections, not the :class:`~gino.engine.GinoConnection` +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries. + +On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use. + +.. note:: + + Another difference to SQLAlchemy here: GINO execution methods always return + final results, while in SQLAlchemy accessing the result may cause further + implicit database accesses. Therefore GINO engine immediately releases the + connection when the execution method on the engine returns, but SQLAlchemy + can only release the connection implicitly when the result data is found + exhausted. + + By immediately releasing a connection, GINO may not release the related raw + connection when the raw connection was reused from another parent + connection. We'll get to this later. + +GINO also supports `implicit execution +`_ +without having to specify an engine or connection explicitly. This is done by +binding the engine to the ``db`` instance, also known as the +:class:`~sqlalchemy.schema.MetaData` or the :class:`~gino.api.Gino` instance. +You may possibly bind a :class:`~gino.engine.GinoConnection` instance, but that +is greatly not recommended because it is very much untested. + +At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +``db`` instance on creation, therefore the models can do implicit execution too +if their ``db`` has a bind. + +Then let's get to some details. + + +Creating Engines +---------------- + +GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous :class:`~gino.engine.GinoEngine` is +just ``gino``, but only available after ``gino`` is imported:: + + import gino, sqlalchemy + + async def main(): + e = await sqlalchemy.create_engine('postgresql://...', strategy='gino') + # e is a GinoEngine + +.. tip:: + + Please read `this SQLAlchemy document + `_ + to learn about writing database URLs. + +Also the GINO strategy replaces the default driver of dialect ``postgresql://`` +from ``psycopg2`` to ``asyncpg``, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +``postgresql+asyncpg://...`` or just ``asyncpg://...``. + +GINO also offers a shortcut as :func:`gino.create_engine`, which only sets the +default strategy to ``gino`` and does nothing more. So here is an identical +example:: + + import gino + + async def main(): + e = await gino.create_engine('postgresql://...') + # e is also a GinoEngine + +As you may have noticed, when using the GINO strategy, +:func:`~sqlalchemy.create_engine` returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default. + +For it is just SQLAlchemy :func:`~sqlalchemy.create_engine`, the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!): + +For Dialect: + +* `isolation_level `_ +* `paramstyle `_ + +For Engine: + +* `echo `_ +* `execution_options `_ +* `logging_name `_ + +While these parameters are discarded by GINO: + +* `module `_ + +In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from :func:`~asyncpg.pool.create_pool`. +For example, we can create an engine without initial connections:: + + e = await gino.create_engine('postgresql://...', min_size=0) + +Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:: + + import sqlalchemy + + metadata = sqlalchemy.MetaData() + metadata.bind = 'postgresql://...' + + # or in short + + metadata = sqlalchemy.MetaData('postgresql://...') + +This implicitly calls :func:`~sqlalchemy.create_engine` under the hood. However +in GINO, creating an engine requires ``await``, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass :class:`~gino.api.Gino`, reverted it to simple assignment:: + + import gino + + db = gino.Gino() + + async def main(): + # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind + engine = await gino.create_engine('postgresql://...') + db.bind = engine + +And provided a shortcut to do so:: + + engine = await db.set_bind('postgresql://...') + +And another simpler shortcut for one-time usage:: + + db = await gino.Gino('postgresql://...') + +To unset a bind and close the engine:: + + engine, db.bind = db.bind, None + await engine.close() + +Or with a shortcut correspondingly:: + + await engine.pop_bind().close() + +Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +Managing Connections +-------------------- + +With a :class:`~gino.engine.GinoEngine` at hand, you can acquire connections +from the pool now:: + + conn = await engine.acquire() + +Don't forget to release it after use:: + + await conn.release() + +Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:: + + async with engine.acquire() as conn: + # play with the connection + +Here ``conn`` is a :class:`~gino.engine.GinoConnection` instance. As mentioned +previously, :class:`~gino.engine.GinoConnection` is mapped to an underlying raw +connection, as shown in following diagram: + +.. image:: ../images/connection.png + :align: center + +Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: ``reuse`` and +``lazy``. They are keyword arguments on :meth:`~gino.engine.GinoEngine.acquire` +and by default switched off. + +reuse +""""" + +When acquiring a :class:`~gino.engine.GinoConnection` (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +:class:`~gino.engine.GinoConnection` (2). This is the default behavior of +:meth:`~gino.engine.GinoConnection.acquire` with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:: + + async with engine.acquire() as conn1: + async with engine.acquire() as conn2: + # conn2 is a completely different connection than conn1 + +But sometimes ``conn2`` may exist in a different method:: + + async def outer(): + async with engine.acquire() as conn1: + await inner() + + async def inner(): + async with engine.acquire() as conn2: + # ... + +And we probably wish ``inner`` could reuse the same raw connection in +``outer`` to save some resource, or borrow a new one if ``inner`` is +individually called without ``outer``:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(conn2=None): + if conn2 is None: + async with engine.acquire() as conn2: + # ... + else: + # the same ... again + +This is exactly the scenario ``reuse`` could be useful. We can simply tell the +:meth:`~gino.engine.GinoConnection.acquire` to reuse the most recent reusable +connection in current context by setting ``reuse=True``, as presented in this +identical example:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(): + async with engine.acquire(reuse=True) as conn2: + # ... + +Back to previous diagram, the blue :class:`~gino.engine.GinoConnection` +instances (3, 4, 6) are "reusing connections" acquired with ``reuse=True``, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that ``acquire(reuse=True)`` always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack. + +.. tip:: + + By context, we are actually referring to the context concept in + `contextvars `_ the + new module in Python 3.7, and its partial backport `aiocontextvars + `_. Simply speaking, you may + treat a series of function calls in a chain as in the same context, even if + there is an ``await``. It's something like a thread local in asyncio. + +:class:`~gino.engine.GinoConnection` (2) may be created through +``acquire(reuse=True)`` too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection. + +Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +:class:`~gino.engine.GinoConnection` can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more. + +lazy +"""" + +As you may have found, :class:`~gino.engine.GinoConnection` (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set ``lazy=True`` on acquire. + +A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, :class:`~gino.engine.GinoConnection` (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + +On implementation level, ``lazy`` is extremely easy in +:meth:`~gino.engine.GinoEngine.acquire`: if ``lazy=False`` then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, :class:`~gino.egnine.GinoConnection` will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all :class:`~gino.engine.GinoConnection` +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + await conn.release(permanent=False) # release (8) + await asyncio.sleep(10) # simulate long I/O work + await conn.scalar('select now()') # re-acquire a new raw connection, + # not necessarily the same (8) + +When used together with ``reuse``, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set ``lazy=False`` on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again: + +.. image:: ../images/connection.png + :align: center + + +reusable +"""""""" + +Usually, you don't have to worry about the two options ``reuse`` and ``lazy``, +using the default :meth:`~gino.engine.GinoEngine.acquire` will always create +a concrete :class:`~gino.engine.GinoConnection` with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use ``reusable=False`` on acquire. As shown in the diagram, the unreusable +:class:`~gino.engine.GinoConnection` is an orphan away from any stack:: + + async with engine.acquire(): # (2) + async with engine.acquire(reusable=False): # the unreusable connection + async with engine.acquire(reuse=True): # (3) + +Unreusable connections can be lazy. But it is usually meaningless to specify +both ``reuse=True`` and ``reusable=False`` at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +``acquire(reuse=True, reusable=False)`` unless you know what it does. + + +current_connection +"""""""""""""""""" + +Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +:attr:`~gino.engine.GinoEngine.current_connection` which is always the reusable +:class:`~gino.engine.GinoConnection` at the top of current stack, or ``None`` +if current stack is empty. + +.. tip:: + + The different between :attr:`~gino.engine.GinoEngine.current_connection` + and :meth:`acquire(reuse=True) ` is, the + latter always produces a :class:`~gino.engine.GinoConnection`, while the + former may not. + + +Executing Queries +----------------- + +Once you have a :class:`~gino.engine.GinoConnection` instance, you can start +executing queries with it. There are 6 variants of the execute method: +:meth:`~gino.engine.GinoConnection.all`, +:meth:`~gino.engine.GinoConnection.first`, +:meth:`~gino.engine.GinoConnection.one`, +:meth:`~gino.engine.GinoConnection.one_or_none`, +:meth:`~gino.engine.GinoConnection.scalar` and +:meth:`~gino.engine.GinoConnection.status`. They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results: + +* :meth:`~gino.engine.GinoConnection.all` returns all results in a + :class:`list`, which may be empty when the query has no result, empty but + still a :class:`list`. +* :meth:`~gino.engine.GinoConnection.first` returns the first result directly, + or ``None`` if there is no result at all. There is usually some optimization + behind the scene to efficiently get only the first result, instead of loading + the full result set into memory. +* :meth:`~gino.engine.GinoConnection.one` returns exactly one result. If there + is no result at all or if there are multiple results, an exception is raised. +* :meth:`~gino.engine.GinoConnection.one_or_none` is similar to + :meth:`~gino.engine.GinoConnection.one`, but it returns ``None`` if there is + no result instead or raising an exception. +* :meth:`~gino.engine.GinoConnection.scalar` is similar to + :meth:`~gino.engine.GinoConnection.first`, it returns the first value of the + first result. Quite convenient to just retrieve a scalar value from database, + like ``NOW()``, ``MAX()``, ``COUNT()`` or whatever generates a single value. + ``None`` is also returned when there is no result, it is up to you how to + distinguish no result and the first value is ``NULL``. +* :meth:`~gino.engine.GinoConnection.status` executes the query and discard all + the query results at all. Instead it returns the execution status line as it + is, usually a textual string. Note, there may be no optimization to only + return the status without loading the results, so make your query generate + nothing if you don't want any result. + +By "result", I meant :class:`~sqlalchemy.engine.RowProxy` of SQLAlchemy - an +immutable row instance with both :class:`tuple` and :class:`dict` interfaces. +Database values are translated twice before they are eventually stored in a +:class:`~sqlalchemy.engine.RowProxy`: first by the database driver (dialect) +from network payload to Python objects (see `Type Conversion +`_ of +how asyncpg does this), second by SQLAlchemy +:meth:`~sqlalchemy.types.TypeEngine.result_processor` depending on the actual +type and dialect. + +The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy :meth:`~sqlalchemy.engine.Connection.execute` (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +``multiparams``, all 4 methods will always return ``None`` discarding all +results. Likewise, the parameter values are processed twice too: first by +:meth:`~sqlalchemy.types.TypeEngine.bind_processor` then the database driver. + +GINO also supports SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execution_options` provided either on +:meth:`engine level `, +:meth:`connection level ` or on +:meth:`queries `. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling ``return_model`` and providing a ``model`` will make +:meth:`~gino.engine.GinoConnection.all` and +:meth:`~gino.engine.GinoConnection.first` return ORM model instance(s) instead +of :class:`~sqlalchemy.engine.RowProxy` instance(s). See also +:meth:`~sqlalchemy.engine.Connection.execution_options` for more information. + +In addition, GINO has an :meth:`~gino.engine.GinoConnection.iterate` method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a `server-side cursor +`_. + + +Implicit Execution +------------------ + +Acquire a :class:`~gino.engine.GinoConnection` and execute queries on it, that +is the most explicit way. You can also execute queries on a +:class:`~gino.engine.GinoEngine` instance. In this case, a connection will be +acquired with ``reuse=True`` for you implicitly, and released after returning:: + + await engine.scalar('select now()') + +Equals to:: + + async with engine.acquire(reuse=True) as conn: + await conn.scalar('select now()') + +This allows you to easily write connectionless code. For example:: + + async def get_now(): + return await engine.scalar('select now()') + + async def main(): + async with engine.acquire(): + now = await get_now() + await engine.status('UPDATE ...') + +In this example, ``main()`` will take only one raw connection. ``get_now()`` +can also work alone out of any ``acquire()`` context, thanks to ``reuse``. + +Furthermore, GINO provides the same query APIs on :class:`~gino.api.Gino` +directly. They are simply delegates to corresponding API methods on the +``bind``. This allows even engine-less programming:: + + db = gino.Gino() + + async def get_now(): + return await db.scalar('select now()') + + async def main(): + async with db.with_bind('postgresql://...'): + now = await get_now() + await db.status('UPDATE ...') + +.. note:: + + In this example we didn't put the two queries in an ``acquire()`` block, so + they might be executed in two different connections. + +At last, the SQLAlchemy `implicit execution +`_ +on queries also work in GINO, under an extension named ``gino``:: + + await users_table.select().gino.all() + +By default, the extension :class:`~gino.api.GinoExecutor` is injected on +:class:`~sqlalchemy.sql.expression.Executable` as a property of name ``gino`` +at the creation of :class:`~gino.api.Gino` instance. Therefore, any +:class:`~sqlalchemy.sql.expression.Executable` object has the ``gino`` +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the ``bind`` of the ``db`` instance. diff --git a/docs/en/1.0/_sources/explanation/why.rst.txt b/docs/en/1.0/_sources/explanation/why.rst.txt new file mode 100644 index 0000000..77455b5 --- /dev/null +++ b/docs/en/1.0/_sources/explanation/why.rst.txt @@ -0,0 +1,244 @@ +Why Asynchronous ORM? +===================== + +Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly. + + +When asyncio Helps +------------------ + +As Mike Bayer - the author of SQLAlchemy - `pointed out +`__, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM". + +The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +:doc:`async`. So the ultimate reason to use asyncio should be the application itself, +not the database. + +For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead. + +.. image:: ../images/263px-Minimum-Tonne.svg.png + :align: right + +Another example is authentication using `OpenID Connect Authorization Code Flow +`__ as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +`Liebig's law `__, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave. + +Arbitrary delays may happen in the database too. In PostgreSQL, you can |LISTEN|_ to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case. + +In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is: + +.. |LISTEN| replace:: ``LISTEN`` +.. _LISTEN: https://www.postgresql.org/docs/current/sql-listen.html + + +How to Access Database +---------------------- + +The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here. + +Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:: + + import asyncio + from fastapi import FastAPI + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + app = FastAPI() + e = create_engine("postgresql://localhost") + Session = sessionmaker(bind=e) + + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +This follows advices found in :ref:`session_faq_whentocreate` from SQLAlchemy - linking +the **session scope** to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is ``kill -9``. + +What just happened is called a `resource starvation +`__. While the first 10 +requests were waiting for the async work (``sleep(0.1)``), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default ``max_overflow=10``) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition. + +The root cause of such starvation is making asynchronous calls in a **transaction +scope** created implicitly by the default ``autocommit=False``. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in **transaction scopes** by explicitly ending them:: + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + session.rollback() # return the connection to avoid starvation + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +Because of the implicit nature of typical ORMs, it's very hard to identify all such +**transaction scopes** and make sure there's no ``await`` in them - you may have started +an implicit transaction by simply accessing a property like ``current_user.name``. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this. + +What about thread pool? + +The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool. + +This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery_. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design. + +In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs. + +.. _Celery: http://www.celeryproject.org/ + + +Asynchronous ORM Done Right +--------------------------- + +I would describe a proper asynchronous ORM as follows: + + +Don't Starve. +^^^^^^^^^^^^^ + +The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything ``async``. + +In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:: + + @app.get("/") + async def read_root(): + async with db.acquire() as conn: # } + async with conn.begin(): # } These two won't block the main thread + now = await db.scalar("SELECT now()") + await asyncio.sleep(0.1) # do some async work + return str(now) + +Even though this code won't cause any resource starvation, using ``await`` within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages: + +1. As the database connection pool is usually much smaller than the asynchronous server + concurrency, this kind of code caps the concurrency down to the DB pool level. +2. Taking a database connection from the pool for nothing is a waste of resource, + especially when the database pool is the shortest stave. +3. Database transactions should be kept short as much as possible. Because long-hanging + transactions may keep certain database locks, leading to performance issues or even + triggering a chain reaction of deadlocks. + + +Be explicit. +^^^^^^^^^^^^ + +With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an ``await`` or ``async with``, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following `the pattern of Twisted +`__, asyncio is already forcing +explicit ``await`` for any asynchronous operations. + +Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example: + +* We could design the ORM model instances to be stateless, so that the users don't have + to learn and worry about maintaining the state of the instances. +* There shouldn't be any "buffered operations" which users could "flush" with a single + statement once for all. +* The user should just give direct one-off commands and the ORM executes them right away. +* Also I think the convenience tooling should be well-balanced, the user doesn't have to + guess or remember what an API means - for example, there're more than one ways to load + a many-to-one relationship, I'd prefer to write the query by myself rather than trying + to remember what "join_without_n_plus_1()" means. + + +Be productive. +^^^^^^^^^^^^^^ + +Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two? + +I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design. + +Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again. diff --git a/docs/en/1.0/_sources/how-to.rst.txt b/docs/en/1.0/_sources/how-to.rst.txt new file mode 100644 index 0000000..00a13df --- /dev/null +++ b/docs/en/1.0/_sources/how-to.rst.txt @@ -0,0 +1,7 @@ +How-to Guides +============= + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/en/1.0/_sources/how-to/alembic.rst.txt b/docs/en/1.0/_sources/how-to/alembic.rst.txt new file mode 100644 index 0000000..aa60714 --- /dev/null +++ b/docs/en/1.0/_sources/how-to/alembic.rst.txt @@ -0,0 +1,135 @@ +Use Alembic +=========== + +Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO. + +To add migrations to project first of all, add alembic as dependency: + +.. code-block:: console + + $ pip install --user alembic + +When you need to set up alembic for your project. + +Prepare sample project. We will have a structure: + + +.. code-block:: console + + alembic_sample/ + my_app/ + models.py + +Inside ``models.py`` define simple DB Model with GINO: + +.. code-block:: python + + from gino import Gino + + db = Gino() + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + +Set up Alembic +^^^^^^^^^^^^^^ + +This will need to be done only once. Go to the main folder of your project +``alembic_sample`` and run: + +.. code-block:: console + + $ alembic init alembic + + +Alembic will create a bunch of files and folders in your project directory. One of them +will be ``alembic.ini``. Open ``alembic.ini`` (you can find it in the main project +folder ``alembic_sample``). Now change property ``sqlalchemy.url =`` with your DB +credentials. Like this: + +.. code-block:: ini + + sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}} + + +Next go to folder ``alembic/`` and open ``env.py`` file. Inside the ``env.py`` file you +need to import the ``db`` object. In our case ``db`` object is ``db`` from ``models`` +modules. This is a variable that links to your ``Gino()`` instance. + +Inside ``alembic/env.py``:: + + from main_app.models import db + + +And change ``target_metadata =`` to:: + + target_metadata = db + +That’s it. We finished setting up Alembic for a project. + +.. note:: + + All ``alembic`` commands must be run always from the folder that contains the + ``alembic.ini`` file. + + +Create first migration revision +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema. + +.. code-block:: console + + $ alembic revision -m "first migration" --autogenerate --head head + +If you have any problems relative to package imports similar to this example: + +.. code-block:: console + + File "alembic/env.py", line 7, in + from main_app.models import db + ModuleNotFoundError: No module named 'main_app' + +Either install your project locally with ``pip install -e .``, ``poetry install`` or +``python setup.py develop``, or add you package to PYTHONPATH, like this: + +.. code-block:: console + + $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample + +After the successful run of ``alembic revision`` in folder ``alembic/versions`` you will +see a file with new migration. + + +Apply migration on DB +^^^^^^^^^^^^^^^^^^^^^ + +Now time to apply migration to DB. It will create tables based on you DB Models. + +.. code-block:: console + + $ alembic upgrade head + +Great. Now you apply your first migration. Congratulations! + +Next time, when you will make any changes in DB models just do: + +.. code-block:: console + + $ alembic revision -m "your migration description" --autogenerate --head head + +And + +.. code-block:: console + + alembic upgrade head + + +Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org diff --git a/docs/en/1.0/_sources/how-to/contributing.rst.txt b/docs/en/1.0/_sources/how-to/contributing.rst.txt new file mode 100644 index 0000000..cbab989 --- /dev/null +++ b/docs/en/1.0/_sources/how-to/contributing.rst.txt @@ -0,0 +1,152 @@ +.. highlight:: console + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/python-gino/gino/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `gino` for local development. + +1. Fork the `gino` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/gino.git + +3. Create a branch for local development:: + + $ cd gino/ + $ git checkout -b name-of-your-bugfix-or-feature + +Now you can make your changes locally. + +4. Create virtual environment. Example for virtualenvwrapper:: + + $ mkvirtualenv gino + +5. Activate the environment and install requirements:: + + $ pip install -r requirements_dev.txt + +6. When you're done making changes, check that your changes pass syntax checks:: + + $ flake8 gino tests + +7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):: + + $ pytest tests + $ tox + +8. For docs run:: + + $ make docs + +It will build and open up docs in your browser. + +9. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +10. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check + https://github.com/python-gino/gino/actions?query=event%3Apull_request + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test -svx tests.test_gino + +By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:: + + CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino'; + CREATE DATABASE gino WITH OWNER = gino; + +Then run the tests like so:: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino + $ py.test + +Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):: + + $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem + $ openssl rsa -in privkey.pem -passin pass:abcd -out server.key + $ openssl req -x509 -in server.req -text -key server.key -out server.crt + $ chmod 600 server.key + $ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + +Terminal 2 (client):: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433 + $ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'" + $ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;" + $ pytest tests/test_aiohttp.py diff --git a/docs/en/1.0/_sources/how-to/crud.rst.txt b/docs/en/1.0/_sources/how-to/crud.rst.txt new file mode 100644 index 0000000..dd23336 --- /dev/null +++ b/docs/en/1.0/_sources/how-to/crud.rst.txt @@ -0,0 +1,5 @@ +==== +CRUD +==== + +**THIS IS A WIP** diff --git a/docs/en/1.0/_sources/how-to/faq.rst.txt b/docs/en/1.0/_sources/how-to/faq.rst.txt new file mode 100644 index 0000000..73c340a --- /dev/null +++ b/docs/en/1.0/_sources/how-to/faq.rst.txt @@ -0,0 +1,389 @@ +Frequently Asked Questions +========================== + +SQLAlchemy 1.4 supports asyncio, what will GINO be? +--------------------------------------------------- + +Starting from 1.4, SQLAlchemy will `support asyncio +`__. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +`greenlet `__. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed. + +Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features: + +* Contextual Connections (see :doc:`../explanation/engine`) +* SQLAlchemy Core-based CRUD models +* The GINO Loader system +* Async MySQL support +* Typing support :sup:`NEW` +* `Trio `__ support :sup:`NEW` +* Execution performance :sup:`NEW` + +As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized. + + +ORM or not ORM? +--------------- + +GINO does perform the Object-Relational Mapping work under the +`Data Mapper Pattern `_, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely `non-ORM way +`__. + + +Can I use features of SQLAlchemy ORM? +------------------------------------- + +SQLAlchemy has `two parts `__: + +* SQLAlchemy Core +* SQLAlchemy ORM + +GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO. + + +How to join? +------------ + +GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know `how to write join in +SQLAlchemy `_. +Especially, `Ádám `_ made some amazing upgrades in +GINO `#113 `_ to make join easier, so +that you can use model classes directly as if they are tables in joining:: + + results = await User.join(Book).select().gino.all() + + +How to connect to database through SSL? +--------------------------------------- + +It depends on the dialect and database driver. For asyncpg, keyword arguments +on :func:`asyncpg.connect() ` are directly +available on :func:`~gino.create_engine` or :meth:`db.set_bind() +`. Therefore, enabling SSL is rather easy:: + + engine = await gino.create_engine(..., ssl=True) + + +What is aiocontextvars and what does it do? +------------------------------------------- + +It is a partial backport of the new built-in module `contextvars +`_ introduced in Python +3.7. In Python 3.5 and 3.6, ``aiocontextvars`` patches ``loop.create_task()`` +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2 + +If you are using Python 3.7, then ``aiocontextvars`` does nothing at all. + +.. note:: + + This answer is for GINO 0.8 and later, please check earlier versions of + this documentation if you are using GINO 0.7. + + +How to define relationships? +---------------------------- + +GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see :doc:`loaders` for more information. + + +How to define index with multiple columns? +------------------------------------------ + +:: + + class User(db.Model): + __tablename__ = 'users' + + first_name = db.Column(db.Unicode()) + last_name = db.Column(db.Unicode()) + + _name_idx = db.Index('index_on_name', 'first_name', 'last_name') + +The ``_name_idx`` is not used. + + +Is there a django admin interface for GINO? +------------------------------------------- + +Not quite yet, please follow `this discussion +`__. + + +How to use multiple databases for different users on the fly? +------------------------------------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. + +In order to use multiple databases, you would need multiple +:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with +lazy engine creation:: + + from asyncio import Future + from contextvars import ContextVar + + from fastapi import FastAPI, Request + from gino import create_engine + from gino.ext.starlette import Gino + + engines = {} + dbname = ContextVar("dbname") + + + class ContextualGino(Gino): + @property + def bind(self): + e = engines.get(dbname.get("")) + if e and e.done(): + return e.result() + else: + return self._bind + + @bind.setter + def bind(self, val): + self._bind = val + + + app = FastAPI() + db = ContextualGino(app) + + + @app.middleware("http") + async def lazy_engines(request: Request, call_next): + name = request.query_params.get("db", "postgres") + fut = engines.get(name) + if fut is None: + fut = engines[name] = Future() + try: + engine = await create_engine("postgresql://localhost/" + name) + except Exception as e: + fut.set_exception(e) + del engines[name] + raise + else: + fut.set_result(engine) + await fut + dbname.set(name) + return await call_next(request) + + + @app.get("/") + async def get(): + return dict(dbname=await db.scalar("SELECT current_database()")) + + +How to load complex query results? +---------------------------------- + +The API doc of :mod:`gino.loader` explains the available loaders, and there're a few +examples in :doc:`loaders` too. + +Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders - +:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Be ware of the :class:`tuple` in ``.gino.load((...))``. + + + +How to do bulk or batch insert / update? +----------------------------------------- + +For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:: + + new_names = ["Austin", "Ali", "Jeff", "Marissa"] + +To quickly insert the names in one query, first construct a dict with the +``{"model_key": "value"}`` format:: + + new_names_dict = [dict(name=new_name) for new_name in new_names] + >> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}] + +Finally, run an insert statement on the model:: + + await User.insert().gino.all(new_names_dict) + + +How to print the executed SQL? +------------------------------ + +GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``. +(Or ``db.set_bind(..., echo=True)``) Please see also `here +`__. + +If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`. + + +How to run ``EXISTS`` SQL? +-------------------------- + +:: + + await db.scalar(db.exists().where(User.email == email).select()) + + +How to work with Alembic? +------------------------- + +The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the +key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py`` +will do. See :doc:`alembic` for more details. + + +How to join the same table twice? +--------------------------------- + +This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +:func:`~gino.crud.CRUDModel.alias`, for example:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.ForeignKey("users.id")) + + Parent = User.alias() + query = User.outerjoin(Parent, User.parent_id == Parent.id).select() + users = await query.gino.load(User.load(parent=Parent)).all() + + +.. _raw-sql: + +How to execute raw SQL with parameters? +--------------------------------------- + +Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments:: + + query = db.text('SELECT * FROM users WHERE id = :id_val') + row = await db.first(query, id_val=1) + +You may even load the rows into model instances:: + + query = query.execution_options(loader=User) + user = await db.first(query, id_val=1) + + +Gino engine is not initialized? +------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before +this, you'll see this error: + +.. code-block:: text + + gino.exceptions.UninitializedError: Gino engine is not initialized. + +You could use either: + +* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the + bind on the :class:`~gino.api.Gino` instance. +* Use one of the Web framework extensions to set the bind for you in usually the server + start-up hook. +* Use explicit ``bind`` for each execution, for example:: + + engine = await create_engine("...") + # ... + user = await User.get(request.user_id, bind=engine) + + +How can I do SQL xxxx in GINO? +------------------------------ + +GINO uses `SQLAlchemy Core `__ queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column +attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column` +instances, you can use them in building your SQLAlchemy Core queries. + +Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above. diff --git a/docs/en/1.0/_sources/how-to/json-props.rst.txt b/docs/en/1.0/_sources/how-to/json-props.rst.txt new file mode 100644 index 0000000..45cf95d --- /dev/null +++ b/docs/en/1.0/_sources/how-to/json-props.rst.txt @@ -0,0 +1,157 @@ +JSON Property +============= + +GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields. + +Quick Start +----------- + +:: + + from gino import Gino + from sqlalchemy.dialects.postgresql import JSONB + + db = Gino() + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + birthday = db.DateTimeProperty() + +The ``age`` and ``birthday`` are JSON properties stored in the ``profile`` column. You +may use them the same way as a normal GINO model field:: + + u = await User.create(name="daisy", age=18) + print(u.name, u.age) # daisy 18 + +.. note:: + + ``profile`` is the default column name for all JSON properties in a model. If you + need a different column name for some JSON properties, you'll need to specify + explicitly:: + + audit_profile = db.Column(JSON, nullable=False, server_default="{}") + + access_log = db.ArrayProperty(prop_name="audit_profile") + abnormal_detected = db.BooleanProperty(prop_name="audit_profile") + +Using JSON properties in queries is supported:: + + await User.query.where(User.age > 16).gino.all() + +This is simply translated into a native JSON query like this: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16) + +Datetime type is very much the same:: + + from datetime import datetime + + await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all() + +And the generated SQL: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2 + -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0)) + +Here's a list of all the supported JSON properties: + ++----------------------------+-----------------------------+-------------+---------------+ +| JSON Property | Python type | JSON type | Database Type | ++============================+=============================+=============+===============+ +| :class:`.StringProperty` | :class:`str` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.IntegerProperty` | :class:`int` | ``number`` | ``int`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.BooleanProperty` | :class:`bool` | ``boolean`` | ``boolean`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.DateTimeProperty` | :class:`~datetime.datetime` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ObjectProperty` | :class:`dict` | ``object`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ArrayProperty` | :class:`list` | ``array`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ + + +Hooks +----- + +JSON property provides 2 instance-level hooks to customize the data:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @age.before_set + def age(self, val): + return val - 1 + + @age.after_get + def age(self, val): + return val + 1 + + u = await User.create(name="daisy", age=18) + print(u.name, u.profile, u.age) # daisy {'age': 17} 18 + +And 1 class-level hook to customize the SQLAlchemy expression of the property:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + height = db.JSONProperty() + + @height.expression + def height(cls, exp): + return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT) + + +Create Index on JSON Properties +------------------------------- + +We'll need to use :meth:`~gino.declarative.declared_attr` to wait until the model class +is initialized. The rest is very much the same as defining a usual index:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @db.declared_attr + def age_idx(cls): + return db.Index("age_idx", cls.age) + +This will lead to the SQL below executed if you run ``db.gino.create_all()``: + +.. code-block:: plpgsql + + CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER)); + +.. warning:: + + Alembic doesn't support auto-generating revisions for functional indexes yet. You'll + need to manually edit the revision. Please follow `this issue + `__ for updates. diff --git a/docs/en/1.0/_sources/how-to/loaders.rst.txt b/docs/en/1.0/_sources/how-to/loaders.rst.txt new file mode 100644 index 0000000..6d82937 --- /dev/null +++ b/docs/en/1.0/_sources/how-to/loaders.rst.txt @@ -0,0 +1,461 @@ +======================== +Loaders and Relationship +======================== + +Loaders are used to load database row results into objects. + +GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined. + + +Model Loader +------------ + +The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:: + + query = db.select([User]) + rows = await query.gino.all() + +In order to load rows into ``User`` objects, you can provide an execution +option ``loader`` with a new :class:`~gino.loader.ModelLoader` instance:: + + from gino.loader import ModelLoader + + query = db.select([User]) + query = query.execution_options(loader=ModelLoader(User)) + users = await query.gino.all() + +The :class:`~gino.loader.ModelLoader` would then load each database row into a +``User`` object. As this is frequently used, GINO made it a shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User.load()) + users = await query.gino.all() + +And another shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User) + users = await query.gino.all() + +.. tip:: + + ``User`` as ``loader`` is transformed into ``ModelLoader(User)`` by + :meth:`Loader.get() `, explained later in "Loader + Expression". + +And again:: + + query = db.select([User]) + users = await query.gino.load(User).all() + +This is identical to the normal CRUD query:: + + users = await User.query.gino.all() + + +Loader Expression +----------------- + +So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than :class:`~gino.loader.ModelLoader`, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders. + +Here is an example using all loaders at once:: + + uid, user, sep, cols = await db.select([User]).gino.load( + ( + User.id, + User, + '|', + lambda row, ctx: len(row), + ) + ).first() + +Let's check this piece by piece. Overall, the argument of +:meth:`~gino.api.GinoExecutor.load` is a tuple. This is interpreted into a +:class:`~gino.loader.TupleLoader`, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a :class:`~gino.loader.TupleLoader` is a tuple. + +:class:`~sqlalchemy.schema.Column` in Loader Expressions are interpreted as +:class:`~gino.loader.ColumnLoader`. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, :class:`~gino.loader.ColumnLoader` uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use :class:`~gino.loader.ColumnLoader`, +you'll have to declare columns for the query:: + + now = db.Column('time', db.DateTime()) + result = await db.first(db.text( + 'SELECT now() AT TIME ZONE \'UTC\'' + ).columns( + now, + ).gino.load( + ('now:', now) + ).query) + print(*result) # now: 2018-04-08 08:23:02.431847 + +Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +:class:`~gino.loader.ModelLoader`. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values. + +.. tip:: + + For a complex loader expression, the same row is given to all loaders, so + it doesn't matter ``User.id`` is already used before the model loader. + +The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +:func:`map`, the return value of the call will be the loaded result. + +At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the ``'|'`` separator, it will show up as the third item +in every result returned by the query. + + +Many-to-One Relationship +------------------------ + +A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + +So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:: + + async for child in Child.load(parent=Parent).gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +As you may have noticed, ``Child.load`` is exactly the shortcut to create +:class:`~gino.loader.ModelLoader` in the very first example. With some +additional keyword arguments, ``Child.load(parent=Parent)`` is still a +:class:`~gino.loader.ModelLoader` for ``Child``, the model loader is at the +same time a **query builder**. It is identical to do this:: + + async for child in Child.load(parent=Parent).query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +The :attr:`~gino.loader.Loader.query` dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The :class:`~gino.loader.Loader` simply forwarded unknown +attributes to its :attr:`~gino.loader.Loader.query`, that's why ``.query`` can +be omitted. + +For :class:`~gino.loader.ModelLoader`, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using :func:`setattr`. For example, ``Parent`` is +interpreted as ``ModelLoader(Parent)`` which loads ``Parent`` instances, and +``Parent`` instances are set as the ``parent`` attribute of the outer ``Child`` +instance. + +.. warning:: + + If multiple children references the same parent, then each child owns a + unique parent instance with identical values. + +.. tip:: + + You don't have to define ``parent`` attribute on ``Child``. But if you do, + you gain the ability to customize how parent is stored or retrieved. For + example, let's store the parent instance as ``_parent``:: + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + _parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + +The query builder works recursively. For :class:`~gino.loader.ModelLoader`, it +uses ``LEFT OUTER JOIN`` to connect the ``FROM`` clauses, in order to achieve +many-to-one scenario. The ``ON`` clause is determined automatically by foreign +keys. You can also customize the ``ON`` clause in case there is no foreign key +(a promise is a promise):: + + loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + async for child in loader.query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +And subloaders can be nested:: + + subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id)) + +By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values. + + +Self Referencing +---------------- + +.. warning:: + + Experimental feature. + +Self referencing is usually used to create a tree-like structure. For example:: + + class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('categories.id')) + +In order to load leaf categories with their parents, an alias is needed:: + + Parent = Category.alias() + +Then the query would be something like this:: + + parents = db.select([Category.parent_id]) + query = Category.load(parent=Parent.on( + Category.parent_id == Parent.id + )).where( + ~Category.id.in_(db.select([Category.alias().parent_id])) + ) + async for c in query.gino.iterate(): + print(f'Leaf: {c.id}, Parent: {c.parent.id}') + +The generated SQL looks like this: + +.. code-block:: SQL + + SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id + FROM categories LEFT OUTER JOIN categories AS categories_1 + ON categories.parent_id = categories_1.id + WHERE categories.id NOT IN ( + SELECT categories_2.parent_id + FROM categories AS categories_2 + ) + + +Other Relationships +------------------- + +GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + @children.setter + def add_child(self, child): + self._children.add(child) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child)).all() + +Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the ``add_child`` setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +``parent.add_child = new_child``. + +Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries. + +GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._child = None + + @property + def child(self): + return self._child + + @child.setter + def child(self, child): + self._child = child + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all() + + +Similarly, you can build many-to-many relationships in the same way:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + def add_child(self, child): + self._children.add(child) + child._parents.add(self) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._parents = set() + + @property + def parents(self): + return self._parents + + + class ParentXChild(db.Model): + __tablename__ = 'parents_x_children' + + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + child_id = db.Column(db.Integer, db.ForeignKey('children.id')) + + + query = Parent.outerjoin(ParentXChild).outerjoin(Child).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all() + +Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ``ParentXChild`` instances. + + +Advanced Usage of Loaders +------------------------- + +You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For `example +`_, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Using alias to get ID-ascending pairs from the same table:: + + ua1 = User.alias() + ua2 = User.alias() + join_query = select([ua1, ua2]).where(ua1.id < ua2.id) + loader = ua1.load('id'), ua2.load('id') + result = await join_query.gino.load(loader).all() + print(result) # e.g. [(1, 2), (1, 3), (2, 3)] + +Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future. diff --git a/docs/en/1.0/_sources/how-to/pool.rst.txt b/docs/en/1.0/_sources/how-to/pool.rst.txt new file mode 100644 index 0000000..4574ab1 --- /dev/null +++ b/docs/en/1.0/_sources/how-to/pool.rst.txt @@ -0,0 +1,27 @@ +=============== +Connection Pool +=============== + +Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +:class:`~gino.dialects.asyncpg.NullPool`), and users can define their own pools. +The base class should be :class:`~gino.dialects.base.Pool`. + +To use non-default pools in raw GINO:: + + from gino.dialects.asyncpg import NullPool + create_engine('postgresql://...', pool_class=NullPool) + +To use non-default pools in extensions (taking Sanic as an example):: + + from gino.dialects.asyncpg import NullPool + from gino.ext.sanic import Gino + + app = sanic.Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_KWARGS = dict( + pool_class=NullPool, + ) + db = Gino() + db.init_app(app) diff --git a/docs/en/1.0/_sources/how-to/schema.rst.txt b/docs/en/1.0/_sources/how-to/schema.rst.txt new file mode 100644 index 0000000..c57eaaa --- /dev/null +++ b/docs/en/1.0/_sources/how-to/schema.rst.txt @@ -0,0 +1,232 @@ +================== +Schema Declaration +================== + +There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy :class:`~sqlalchemy.schema.Table`. + + +GINO Engine +----------- + +This is the minimized way to use GINO - using only +:class:`~gino.engine.GinoEngine` (and :class:`~gino.engine.GinoConnection` +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two. + +For example, the table declaration is the same as SQLAlchemy core `tutorial +`_:: + + from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey + + metadata = MetaData() + + users = Table( + 'users', metadata, + + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + ) + + addresses = Table( + 'addresses', metadata, + + Column('id', Integer, primary_key=True), + Column('user_id', None, ForeignKey('users.id')), + Column('email_address', String, nullable=False) + ) + +.. note:: + + When using GINO Engine only, it is usually your own business to create the + tables with either :meth:`~sqlalchemy.schema.MetaData.create_all` on a + normal non-async SQLAlchemy engine, or using Alembic. However it is still + possible to be done with GINO if it had to:: + + import gino + from gino.schema import GinoSchemaVisitor + + async def main(): + engine = await gino.create_engine('postgresql://...') + await GinoSchemaVisitor(metadata).create_all(engine) + +Then, construct queries, in SQLAlchemy core too:: + + ins = users.insert().values(name='jack', fullname='Jack Jones') + +So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:: + + async def main(): + engine = await gino.create_engine('postgresql://localhost/gino') + conn = await engine.acquire() + await conn.status(ins) + print(await conn.all(users.select())) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Here :func:`~gino.create_engine` creates a :class:`~gino.engine.GinoEngine`, +then :meth:`~gino.engine.GinoEngine.acquire` checks out a +:class:`~gino.engine.GinoConnection`, and +:meth:`~gino.engine.GinoConnection.status` executes the insert and returns the +status text. This works similarly as SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execute` - they take the same parameters +but return a bit differently. There are also other similar query APIs: + +* :meth:`~gino.engine.GinoConnection.all` returns a list of + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.first` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.one` returns one + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.one_or_none` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.scalar` returns a single value, or + ``None`` +* :meth:`~gino.engine.GinoConnection.iterate` returns an asynchronous iterator + which yields :class:`~sqlalchemy.engine.RowProxy` + +Please go to their API for more information. + + +GINO Core +--------- + +In previous scenario, :class:`~gino.engine.GinoEngine` must not be set to +:attr:`metadata.bind ` because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of :class:`~sqlalchemy.schema.MetaData` as :class:`~gino.api.Gino`, +usually instantiated globally under the name of ``db``. It can be used as a +normal :class:`~sqlalchemy.schema.MetaData` still offering some conveniences: + +* It delegates most public types you can access on ``sqlalchemy`` +* It works with both normal SQLAlchemy engine and asynchronous GINO engine +* It exposes all query APIs on :class:`~gino.engine.GinoConnection` level +* It injects two ``gino`` extensions on SQLAlchemy query clauses and schema + items, allowing short inline execution like ``users.select().gino.all()`` +* It is also the entry for the third scenario, see later + +Then we can achieve previous scenario with less code like this:: + + from gino import Gino + + db = Gino() + + users = db.Table( + 'users', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('name', db.String), + db.Column('fullname', db.String), + ) + + addresses = db.Table( + 'addresses', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('user_id', None, db.ForeignKey('users.id')), + db.Column('email_address', db.String, nullable=False) + ) + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await users.insert().values( + name='jack', + fullname='Jack Jones', + ).gino.status() + print(await users.select().gino.all()) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but ``sqlalchemy`` seems +never imported. This is useful when ORM is unwanted. + +.. tip:: + + `asyncpgsa `_ does the same thing, + but in a conceptually reversed way - instead of having asyncpg work for + SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that + way too because GINO is inspired by asyncpgsa). Either way works fine, it's + just a matter of taste of whose API style to use, SQLAlchemy or asyncpg. + + +GINO ORM +-------- + +If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:: + + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + fullname = db.Column(db.String) + + + class Address(db.Model): + __tablename__ = 'addresses' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(None, db.ForeignKey('users.id')) + email_address = db.Column(db.String, nullable=False) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await User.create(name='jack', fullname='Jack Jones') + print(await User.query.gino.all()) + # Outputs: [] + +.. important:: + + The ``__tablename__`` is a mandatory field to define a concrete model. + +As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in ``db``. The ``class`` style is just +more declarative. Instead of ``users.c.name``, you can now access the column by +``User.name``. The implicitly created :class:`~sqlalchemy.schema.Table` is +available at ``User.__table__`` and ``Address.__table__``. You can use anything +that works in GINO core here. + +.. note:: + + Column names can be different as a class property and database column. + For example, name can be declared as + ``nickname = db.Column('name', db.Unicode(), default='noname')``. In this + example, ``User.nickname`` is used to access the column, while in database, + the column name is ``name``. + + What's worth mentioning is where raw SQL statements are used, or + ``TableClause`` is involved, like ``User.insert()``, the original name is + required to be used, because in this case, GINO has no knowledge about the + mappings. + +.. tip:: + + ``db.Model`` is a dynamically created parent class for your models. It is + associated with the ``db`` on initialization, therefore the table is put in + the very ``db`` when you declare your model class. + +Things become different when it comes to CRUD. You can use model level methods +to directly :meth:`~gino.crud.CRUDModel.create` a model instance, instead of +inserting a new row. Or :meth:`~gino.crud.CRUDModel.delete` a model instance +without needing to specify the where clause manually. Query returns model +instances instead of :class:`~sqlalchemy.engine.RowProxy`, and row values are +directly available as attributes on model instances. See also: +:doc:`/how-to/crud`. + +After all, :class:`~gino.engine.GinoEngine` is always in use. Next let's dig +more into it. diff --git a/docs/en/1.0/_sources/how-to/transaction.rst.txt b/docs/en/1.0/_sources/how-to/transaction.rst.txt new file mode 100644 index 0000000..558a5ae --- /dev/null +++ b/docs/en/1.0/_sources/how-to/transaction.rst.txt @@ -0,0 +1,116 @@ +=========== +Transaction +=========== + +It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an ``await`` will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it. + + +Basic usage +----------- + +Transactions belong to :class:`~gino.engine.GinoConnection`. The most common +way to use transactions is through an ``async with`` statement:: + + async with connection.transaction() as tx: + await connection.status('INSERT INTO mytable VALUES(1, 2, 3)') + +This guarantees a transaction is opened when entering the ``async with`` block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at :attr:`~gino.transaction.GinoTransaction.raw_transaction`, but in +most cases you don't need to touch it. + +GINO provides two convenient shortcuts to end the transaction early: + +* :meth:`tx.raise_commit() ` +* :meth:`tx.raise_rollback() ` + +They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the ``async with`` block after +:meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` is skipped. The +internal exception is inherited from :exc:`BaseException` so that normal ``try +... except Exception`` block can't trap it. This exception stops propagating at +the end of ``async with`` block, so you don't need to worry about handling it. + +Transactions can also be started on a :class:`~gino.engine.GinoEngine`:: + + async with engine.transaction() as tx: + await engine.status('INSERT INTO mytable VALUES(1, 2, 3)') + +Here a :class:`~gino.engine.GinoConnection` is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The :class:`~gino.engine.GinoConnection` instance is accessible at +:attr:`tx.connection `. Other than +that, everything else is the same. + +.. important:: + + The implicit connection is by default borrowed with ``reuse=True``. That + means using :meth:`~gino.engine.GinoEngine.transaction` of + :class:`~gino.engine.GinoEngine` within a connection context is the same as + calling :meth:`~gino.engine.GinoConnection.transaction` of the current + connection without having to reference it, no separate connection shall be + created. + +Similarly, if your :class:`~gino.api.Gino` instance has a bind, you may also do +the same on it:: + + async with db.transaction() as tx: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + + +Nested Transactions +------------------- + +Transactions can be nested, nested transaction will create a `savepoint +`_ as for +now on asyncpg. A similar example from asyncpg doc:: + + async with connection.transaction() as tx1: + await connection.status('CREATE TABLE mytab (a int)') + + # Create a nested transaction: + async with connection.transaction() as tx2: + await connection.status('INSERT INTO mytab (a) VALUES (1), (2)') + # Rollback the nested transaction: + tx2.raise_rollback() + + # Because the nested transaction was rolled back, there + # will be nothing in `mytab`. + assert await connection.all('SELECT a FROM mytab') == [] + +As you can see, the :meth:`~gino.transaction.GinoTransaction.raise_rollback` +breaks only the ``async with`` block of the specified ``tx2``, the outer +transaction ``tx1`` just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate. + +Because of the default reusing behavior, transactions on engine or ``db`` +follows the same nesting rules. Please see +:class:`~gino.transactions.GinoTransaction` for more information. + + +Manual Control +-------------- + +Other than using ``async with``, you can also manually control the +transaction:: + + tx = await connection.transaction() + try: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + await tx.commit() + except Exception: + await tx.rollback() + raise + +You can't use :meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` here, similarly it is +prohibited to use :meth:`~gino.transaction.GinoTransaction.commit` and +:meth:`~gino.transaction.GinoTransaction.rollback` in an ``async with`` block. diff --git a/docs/en/1.0/_sources/index.rst.txt b/docs/en/1.0/_sources/index.rst.txt new file mode 100644 index 0000000..1512377 --- /dev/null +++ b/docs/en/1.0/_sources/index.rst.txt @@ -0,0 +1,127 @@ +Welcome to GINO's documentation! +================================ + +.. image:: https://img.shields.io/pypi/v/gino?logo=python&logoColor=white&color=3E6CDE&style=flat-square + :alt: PyPI Release Version + :target: https://pypi.python.org/pypi/gino + +.. image:: https://img.shields.io/github/workflow/status/python-gino/gino/test?label=test&logo=github&color=3E6CDE&style=flat-square + :alt: GitHub Workflow Status for tests + :target: https://github.com/python-gino/gino/actions?query=workflow%3Atest + +.. image:: https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy&color=3E6CDE&style=flat-square + :alt: Codacy coverage + :target: https://app.codacy.com/gh/python-gino/gino/dashboard + +.. image:: https://img.shields.io/badge/Dependabot-active-brightgreen?logo=dependabot&color=3E6CDE&style=flat-square + :target: https://app.dependabot.com/accounts/python-gino/projects/129260 + :alt: Dependabot + + +GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy_ core for Python asyncio_. Now (early 2020) GINO supports only one +dialect asyncpg_. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _asyncpg: https://github.com/MagicStack/asyncpg + + +.. cssclass:: boxed-nav + +* .. image:: images/tutorials.svg + + :doc:`tutorials` + + Lessons for the newcomer to get started + +* .. image:: images/how-to.svg + + :doc:`how-to` + + Solve specific problems by steps + +* .. image:: images/explanation.svg + + :doc:`explanation` + + Explains the background and context + +* .. image:: images/reference.svg + + :doc:`reference` + + Describes the software as it is + + +Useful Links +------------ + +.. cssclass:: boxed-nav + +* .. image:: images/github.svg + + `Source Code `_ + + https://github.com/python-gino/gino + +* .. image:: images/community.svg + + `Community `_ + + https://gitter.im/python-gino/Lobby + +* .. image:: images/open-source.svg + + `BSD license `_ + + GINO is free software + +* .. image:: images/python.svg + + `Download `_ + + Download GINO from PyPI + + +.. cssclass:: divio + +Sections by `Divio `_. + +.. toctree:: + :caption: Tutorials + :maxdepth: 1 + :glob: + :hidden: + + tutorials + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* + +.. toctree:: + :caption: How-to Guides + :maxdepth: 1 + :glob: + :hidden: + + how-to + how-to/* + +.. toctree:: + :caption: Explanation + :maxdepth: 1 + :glob: + :hidden: + + explanation + explanation/* + +.. toctree:: + :caption: Reference + :maxdepth: 1 + :glob: + :hidden: + + reference + reference/* diff --git a/docs/en/1.0/_sources/reference.rst.txt b/docs/en/1.0/_sources/reference.rst.txt new file mode 100644 index 0000000..977912b --- /dev/null +++ b/docs/en/1.0/_sources/reference.rst.txt @@ -0,0 +1,7 @@ +Reference +========= + +.. toctree:: + :glob: + + reference/* diff --git a/docs/en/1.0/_sources/reference/api.rst.txt b/docs/en/1.0/_sources/reference/api.rst.txt new file mode 100644 index 0000000..23fc548 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api.rst.txt @@ -0,0 +1,6 @@ +API Reference +============= + +.. toctree:: + + api/gino diff --git a/docs/en/1.0/_sources/reference/api/gino.aiocontextvars.rst.txt b/docs/en/1.0/_sources/reference/api/gino.aiocontextvars.rst.txt new file mode 100644 index 0000000..4728456 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.aiocontextvars.rst.txt @@ -0,0 +1,7 @@ +gino.aiocontextvars module +========================== + +.. automodule:: gino.aiocontextvars + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.api.rst.txt b/docs/en/1.0/_sources/reference/api/gino.api.rst.txt new file mode 100644 index 0000000..8825a74 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.api.rst.txt @@ -0,0 +1,7 @@ +gino.api module +=============== + +.. automodule:: gino.api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.crud.rst.txt b/docs/en/1.0/_sources/reference/api/gino.crud.rst.txt new file mode 100644 index 0000000..9683c99 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.crud.rst.txt @@ -0,0 +1,7 @@ +gino.crud module +================ + +.. automodule:: gino.crud + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.declarative.rst.txt b/docs/en/1.0/_sources/reference/api/gino.declarative.rst.txt new file mode 100644 index 0000000..8ec0bdd --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.declarative.rst.txt @@ -0,0 +1,7 @@ +gino.declarative module +======================= + +.. automodule:: gino.declarative + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.dialects.asyncpg.rst.txt b/docs/en/1.0/_sources/reference/api/gino.dialects.asyncpg.rst.txt new file mode 100644 index 0000000..9423016 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.dialects.asyncpg.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.asyncpg module +============================ + +.. automodule:: gino.dialects.asyncpg + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.dialects.base.rst.txt b/docs/en/1.0/_sources/reference/api/gino.dialects.base.rst.txt new file mode 100644 index 0000000..c512657 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.dialects.base.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.base module +========================= + +.. automodule:: gino.dialects.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.dialects.rst.txt b/docs/en/1.0/_sources/reference/api/gino.dialects.rst.txt new file mode 100644 index 0000000..8e9bc19 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.dialects.rst.txt @@ -0,0 +1,19 @@ +gino.dialects package +===================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects.asyncpg + gino.dialects.base + +Module contents +--------------- + +.. automodule:: gino.dialects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.engine.rst.txt b/docs/en/1.0/_sources/reference/api/gino.engine.rst.txt new file mode 100644 index 0000000..0075c29 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.engine.rst.txt @@ -0,0 +1,7 @@ +gino.engine module +================== + +.. automodule:: gino.engine + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.exceptions.rst.txt b/docs/en/1.0/_sources/reference/api/gino.exceptions.rst.txt new file mode 100644 index 0000000..d3659da --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.exceptions.rst.txt @@ -0,0 +1,7 @@ +gino.exceptions module +====================== + +.. automodule:: gino.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.ext.rst.txt b/docs/en/1.0/_sources/reference/api/gino.ext.rst.txt new file mode 100644 index 0000000..fe56088 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.ext.rst.txt @@ -0,0 +1,10 @@ +gino.ext package +================ + +Module contents +--------------- + +.. automodule:: gino.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.json_support.rst.txt b/docs/en/1.0/_sources/reference/api/gino.json_support.rst.txt new file mode 100644 index 0000000..5a7c069 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.json_support.rst.txt @@ -0,0 +1,7 @@ +gino.json\_support module +========================= + +.. automodule:: gino.json_support + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.loader.rst.txt b/docs/en/1.0/_sources/reference/api/gino.loader.rst.txt new file mode 100644 index 0000000..06ce463 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.loader.rst.txt @@ -0,0 +1,7 @@ +gino.loader module +================== + +.. automodule:: gino.loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.rst.txt b/docs/en/1.0/_sources/reference/api/gino.rst.txt new file mode 100644 index 0000000..3207440 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.rst.txt @@ -0,0 +1,37 @@ +gino package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects + gino.ext + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.aiocontextvars + gino.api + gino.crud + gino.declarative + gino.engine + gino.exceptions + gino.json_support + gino.loader + gino.schema + gino.strategies + gino.transaction + +Module contents +--------------- + +.. automodule:: gino + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.schema.rst.txt b/docs/en/1.0/_sources/reference/api/gino.schema.rst.txt new file mode 100644 index 0000000..36fbb62 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.schema.rst.txt @@ -0,0 +1,7 @@ +gino.schema module +================== + +.. automodule:: gino.schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.strategies.rst.txt b/docs/en/1.0/_sources/reference/api/gino.strategies.rst.txt new file mode 100644 index 0000000..01de55b --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.strategies.rst.txt @@ -0,0 +1,7 @@ +gino.strategies module +====================== + +.. automodule:: gino.strategies + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/api/gino.transaction.rst.txt b/docs/en/1.0/_sources/reference/api/gino.transaction.rst.txt new file mode 100644 index 0000000..2a28e55 --- /dev/null +++ b/docs/en/1.0/_sources/reference/api/gino.transaction.rst.txt @@ -0,0 +1,7 @@ +gino.transaction module +======================= + +.. automodule:: gino.transaction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.0/_sources/reference/extensions.rst.txt b/docs/en/1.0/_sources/reference/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/en/1.0/_sources/reference/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/en/1.0/_sources/reference/extensions/sanic.rst.txt b/docs/en/1.0/_sources/reference/extensions/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/en/1.0/_sources/reference/extensions/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/en/1.0/_sources/reference/extensions/starlette.rst.txt b/docs/en/1.0/_sources/reference/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/en/1.0/_sources/reference/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/en/1.0/_sources/reference/extensions/tornado.rst.txt b/docs/en/1.0/_sources/reference/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/en/1.0/_sources/reference/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/en/1.0/_sources/reference/history.rst.txt b/docs/en/1.0/_sources/reference/history.rst.txt new file mode 100644 index 0000000..d1ee7bb --- /dev/null +++ b/docs/en/1.0/_sources/reference/history.rst.txt @@ -0,0 +1,609 @@ +======= +History +======= + +GINO 1.0 +-------- + +Migrating to GINO 1.0 +^^^^^^^^^^^^^^^^^^^^^ + +GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras: + ++------------------------+---------------------------------+ +| Extension Module | Installation in GINO 1.0 | ++========================+=================================+ +| ``gino.ext.starlette`` | ``pip install gino[starlette]`` | ++------------------------+---------------------------------+ +| ``gino.ext.aiohttp`` | ``pip install gino[aiohttp]`` | ++------------------------+---------------------------------+ +| ``gino.ext.sanic`` | ``pip install gino[sanic]`` | ++------------------------+---------------------------------+ +| ``gino.ext.tornado`` | ``pip install gino[tornado]`` | ++------------------------+---------------------------------+ +| ``gino.ext.quart`` | ``pip install gino[quart]`` | ++------------------------+---------------------------------+ + +The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed ``gino[starlette]``:: + + from gino.ext.starlette import Gino + +GINO 1.0 switched to `Poetry `__ for package and +dependency management and started to use the +`src layout `__. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process: + +* Source files are now located under ``src`` directory. +* The src dist on PyPI does not include tests, docs and some other files due to + a limitation of Poetry. + +1.0.1 (2020-06-08) +^^^^^^^^^^^^^^^^^^ + +* Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4) +* Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672) +* Multiple JSON property fixes (#661 #662 #695) +* Fixed extension typing issue (#673 #674) +* Fixed model override behavior (#694) +* Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696) + +1.0.0 (2020-04-26) +^^^^^^^^^^^^^^^^^^ + +* Switched to Poetry for package and dependency management. +* [Breaking] Moved built-in extension modules to separate PyPI packages. +* Switched to src layout. +* Switched to black code style. +* Better documentation. +* [Breaking] ``none_as_none()`` is now always enabled. +* Added representation method for engine. +* Protected the URL instance fed to ``set_bind()`` from manipulation. +* Replaced some ``assert`` with ``AssertionError`` (#258 #655) + + +GINO 0.8 +-------- + +This is also version 1.0 release candidate. + +Migrating to GINO 0.8 +^^^^^^^^^^^^^^^^^^^^^ + +1. contextvars +"""""""""""""" + +We introduced aiocontextvars_ 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the ``enable_inherit()`` or +``disable_inherit()`` calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created **after** importing ``gino`` or ``aiocontextvars``, or the patch +won't work correctly. + +There is nothing to worry about in Python 3.7. + +2. none_as_none +""""""""""""""" + +When GINO tries to load a row with all ``NULL`` values into an instance, it +will now by default return ``None`` instead of an instance with all ``None`` +attributes. To recover the default behavior of 0.7, please specify +``none_as_none(False)`` in affected model loader. + +This is especially applicable to relationship sub-loaders - if the sub-loader +found it all ``NULL``, no instance will be set to parent instance. For +example:: + + child = await Child.load(parent=Parent).query.gino.first() + +If ``child.parent_id`` is ``NULL`` in database, then the ``child`` instance +won't be called with any ``setattr(child, 'parent', ...)`` at all. (If you need +``child.parent == None`` in this case, consider setting default value +``parent = None`` in child model.) + +Please note, it is deprecated to disable ``none_as_none``, and disabling will +be removed in GINO 1.0. + +0.8.7 (2020-04-19) +^^^^^^^^^^^^^^^^^^ + +* Improved error handling when attribute names collide (Contributed by Reskov in #637 #638) +* Fixed ``with_bind`` usability in aiohttp extension (#518) + +0.8.6 (2020-02-10) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``JSONPathType`` bind processor for asyncpg (#609) +* Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600) +* Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629) +* Added ``distinct()`` to ``Alias`` (#628) + +0.8.5 (2019-11-19) +^^^^^^^^^^^^^^^^^^ + +* Improved support for ``__tablename__`` in ``declared_attr`` (Contributed by Roald Storm in #592) + +0.8.4 (2019-11-09) +^^^^^^^^^^^^^^^^^^ + +* Better loader support for models in subqueries (#573 #585) +* Allowed ``__tablename__`` to be a ``declared_attr`` (#579 #582) +* Fixed Sanic 19.9.0 compatibility (#569) +* Added one() and one_or_none() (Contributed by Ilaï Deutel in #577) +* Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538) +* Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533) +* Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520) +* Fixed Grammar (Contributed by Simeon J Morgan in #504) + +0.8.3 (2019-06-06) +^^^^^^^^^^^^^^^^^^ + +* Fixed deprecated warnings in asyncpg dialect and aiohttp (#425) +* Customizable db attribute name in aiohttp app instance (#457) +* Added Starlette support (#486) + +0.8.2 (2019-03-07) +^^^^^^^^^^^^^^^^^^ + +* Added exception for unknown JSON properties (#406 #408) +* Supported Quart 0.7 (#411) +* Accepted kwargs for db init in extensions (#407 #427) +* Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440) +* Added NullPool (#437 #441) +* Unpinned dependency versions (#447) +* Added support for SQLAlchemy 1.3 (#433 #451) + +0.8.1 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Alias supported ``Label`` (#365) +* Docs update (#308, 4c59ad, #401 by Pascal van Kooten) +* Version requirement for SQLAlchemy is updated to ``>=1.2`` (#378 #382) +* Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) + * And all other extensions (#395) +* Supported Tornado 5 (#396, also thanks to Vladimir Goncharov) +* Fixed custom JSON/JSONB type support (#402 #403) + +(Most fixes done by Tony Wang) + +0.8.0 (2018-10-16) +^^^^^^^^^^^^^^^^^^ + +* Welcome Tony Wang to the maintenance team (#335) +* Allowed custom column names (#261 #297) +* Allowed column instance in ``model.load()`` (Contributed by Jekel in #323) +* [Breaking] Upgraded to aiocontextvars 0.2.0 (#333) +* Fixed bug that the same empty stack is shared between sub-tasks (#313 #334) +* [Breaking] Made ``none_as_none()`` the default behavior (#351) +* Bug fixes and docs update + + +GINO 0.7 +-------- + +This is also version 1.0 beta 3. + +0.7.7 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Backported fix for custom JSON/JSONB type support (#402 #403) + +0.7.6 (2018-08-26) +^^^^^^^^^^^^^^^^^^ + +* Updated library support (Contributed by Tony Wang in #275 #309) +* Added ``none_as_none()`` (#281 #282) +* Added ``ARRAY`` alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289) +* Added ``Model.lookup()`` to prevent updating whole table without primary key (#287 #288) +* Added ``DB_ECHO`` in extension options (Contributed by Mykyta Holubakha in #298) +* Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305) +* Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304) +* Fixed to raise ``UninitializedError`` if bind is ``None`` (Contributed by Tony Wang in #307 #310) + +0.7.5 (2018-07-26) +^^^^^^^^^^^^^^^^^^ + +* Added friendly error message when using abstract models by mistake (#224) +* Supported Python 3.7 (Contributed by Tony Wang in #265) +* Updated documentation +* Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280) + +0.7.4 (2018-06-10) +^^^^^^^^^^^^^^^^^^ + +* Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228) +* Added Quart support (#213) +* Fixed Tornado options parsing (#231) +* Improved coding style and test coverage + +0.7.3 (2018-05-19) +^^^^^^^^^^^^^^^^^^ + +* Fix for failing binary type (#225) + +0.7.2 (2018-05-15) +^^^^^^^^^^^^^^^^^^ + +* Added prepared statement support (#14) +* Added dsn in extension config (Contributed by Yurii Shtrikker in #215) + +0.7.1 (2018-05-03) +^^^^^^^^^^^^^^^^^^ + +* Added support for inline model constraints (Contributed by Kinware in #198) +* Added docs and tests for using SSL (#202) +* Added ``declared_attr`` (#204) +* Allowed ``ModelLoader`` passively load partial model (#216) + +0.7.0 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Added Python 3.5 support (#187) +* Added support to use ``dict`` as ident for ``Model.get`` (#192) +* Added result loader (partial relationship support) (#13) +* Added documentation on relationship and transaction (#146) + + +GINO 0.6 +-------- + +This is also version 1.0 beta 2. + +Migrating to GINO 0.6 +^^^^^^^^^^^^^^^^^^^^^ + +1. Task Local +""""""""""""" + +We created a new Python package aiocontextvars_ from previous ``local.py``. If +you made use of the task local features, you should install this package. + +Previous ``gino.enable_task_local()`` and ``gino.disable_task_local()`` are +replaced by ``aiocontextvars.enable_inherit()`` and +``aiocontextvars.disable_inherit()``. However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars_ by default offers task +local even without ``enable_inherit()``, which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars_ is installed. + +There is no ``gino.get_local()`` and ``gino.reset_local()`` relevant in +aiocontextvars_. The similar thing is ``aiocontextvars.ContextVar`` instance +through its ``get()``, ``set()`` and ``delete()`` methods. + +Previous ``gino.is_local_root()`` is now +``not aiocontextvars.Context.current().inherited``. + +2. Engine +""""""""" + +GINO 0.6 hides ``asyncpg.Pool`` behind the new SQLAlchemy-alike +``gino.GinoEngine``. Instead of doing this in 0.5:: + + async with db.create_pool('postgresql://...') as pool: + # your code here + +You should change it to this in 0.6:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +This equals to:: + + engine = await gino.create_engine('postgresql://...') + db.bind = engine + try: + # your code here + finally: + db.bind = None + await engine.close() + +Or:: + + engine = await db.set_bind('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Or even this:: + + db = await gino.Gino('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Choose whichever suits you the best. + +Obviously ``GinoEngine`` doesn't provide ``asyncpg.Pool`` methods directly any +longer, but you can get the underlying ``asyncpg.Pool`` object through +``engine.raw_pool`` property. + +``GinoPool.get_current_connection()`` is now changed to ``current_connection`` +property on ``GinoEngine`` instances to support multiple engines. + +``GinoPool.execution_option`` is gone, instead ``update_execution_options()`` +on ``GinoEngine`` instance is available. + +``GinoPool().metadata`` is gone, ``dialect`` is still available. + +``GinoPool.release()`` is removed in ``GinoEngine`` and ``Gino``, the +``release()`` method on ``GinoConnection`` object should be used instead. + +These methods exist both in 0.5 ``GinoPool`` and 0.6 ``GinoEngine``: +``close()``, ``acquire()``, ``all()``, ``first()``, ``scalar()``, ``status()``. + +3. GinoConnection +""""""""""""""""" + +Similarly, ``GinoConnection`` in 0.6 is no longer a subclass of +``asyncpg.Connection``, instead it has a ``asyncpg.Connection`` instance, +accessable through ``GinoConnection.raw_connection`` property. + +``GinoConnection.metadata`` is deleted in 0.6, while ``dialect`` remained. + +``GinoConnection.execution_options()`` is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior. + +``GinoConnection.release()`` is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +``permanent=False`` to remain its previous behavior. + +And ``all()``, ``first()``, ``scalar()``, ``status()``, ``iterate()``, +``transaction()`` remained in 0.6. + +4. Query API +"""""""""""" + +All five query APIs ``all()``, ``first()``, ``scalar()``, ``status()``, +``iterate()`` now accept the same parameters as SQLAlchemy ``execute()``, +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter ``bind`` if they did. Just use the API on the ``GinoEngine`` or +``GinoConnection`` object instead. + +5. Transaction +"""""""""""""" + +Transaction interface is rewritten. Now in 0.6, a ``GinoTransaction`` object is +provided consistently from all 3 methods:: + + async with db.transaction() as tx: + # within transaction + + async with engine.transaction() as tx: + # within transaction + + async with engine.acquire() as conn: + async with conn.transaction() as tx: + # within transaction + +And different usage with ``await``:: + + tx = await db.transaction() + try: + # within transaction + await tx.commit() + except: + await tx.rollback() + raise + +The ``GinoConnection`` object is available at ``tx.connection``, while +underlying transaction object from database driver is available at +``tx.transaction`` - for asyncpg it is an ``asyncpg.transaction.Transaction`` +object. + +0.6.6 (2018-05-18) +^^^^^^^^^^^^^^^^^^ + +* Backported a fix for failing binary type (#225) + +0.6.5 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Abandoned 0.6.4 and keep 0.6.x stable +* Backported doc for transaction + +0.6.4 (2018-04-16) +^^^^^^^^^^^^^^^^^^ + +Abandoned version, please use 0.7.0 instead. + +0.6.3 (2018-04-08) +^^^^^^^^^^^^^^^^^^ + +* Added aiohttp support +* Added support for calling ``create()`` on model instances (Contributed by Kinware in #178 #180) +* Fixed ``get()`` by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184) + +0.6.2 (2018-03-24) +^^^^^^^^^^^^^^^^^^ + +* Fixed SQLAlchemy prefetch issue (#141) +* Fixed issue that mixin class on Model not working (#174) +* Added more documentation (Thanks Olaf Conradi for reviewing) + +0.6.1 (2018-03-18) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``create`` and ``drop`` for ``Enum`` type (#160) +* A bit more documentation (#159) + +0.6.0 (2018-03-14) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] API Refactored, ``Pool`` replaced with ``Engine`` + + * New API ``Engine`` replaced asyncpg ``Pool`` (#59) + * Supported different dialects, theoretically + * Used aiocontextvars_ instead of builtin task local (#89) +* [Breaking] Fixed query API with ``multiparams`` (executemany) to return correctly (#20) +* [Breaking] The query methods no longer accept the parameter ``bind`` +* [Breaking] ``Gino`` no longer exposes ``postgresql`` types +* Added ``echo`` on engine (#142) +* Added tests to cover 80% of code +* Added ``gino`` extension on ``SchemaItem`` for ``create_all`` and so on (#76 #106) +* Added ``gino`` extension on model classes for ``create()`` or ``drop()`` +* Added ``_update_request_cls`` on ``CRUDModel`` (#147) +* Rewrote the documentation (#146) + +.. _aiocontextvars: https://github.com/fantix/aiocontextvars + + +GINO 0.5 +-------- + +This is also version 1.0 beta 1. + +0.5.8 (2018-02-14) +^^^^^^^^^^^^^^^^^^ + +* Preparing for 0.6.0 which will be a breaking release +* Fixed wrong value of ``Enum`` in creation (Contributed by Sergey Kovalev in #126) + +0.5.7 (2017-11-24) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.6. + +* Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114) +* Added ``Model.outerjoin`` + +0.5.6 (2017-11-23) +^^^^^^^^^^^^^^^^^^ + +* Changed to use unnamed statement when possible (#80 #90) +* Added more example (Contributed by Kentoseth in #109) +* Added ``Model.join`` and made ``Model`` selectable (Contributed by Ádám Barancsuk in #112 #113) + +0.5.5 (2017-10-18) +^^^^^^^^^^^^^^^^^^ + +* Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87) +* Added ability to reset local storage (#84) +* Fixed bug in JSON property update +* Added update chaining feature + +0.5.4 (2017-10-04) +^^^^^^^^^^^^^^^^^^ + +* Updated example (Contributed by Kinware in #75) +* Added ``Model.insert`` (Contributed by Neal Wang in #63) +* Fixed issue that non-lazy acquiring fails dirty (#79) + +0.5.3 (2017-09-23) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``no module named cutils`` error (Contributed by Vladimir Goncharov in #73) + +0.5.2 (2017-09-10) +^^^^^^^^^^^^^^^^^^ + +* Added missing driver name on dialect (#67) +* Fixed dialect to support native decimal type (#67) + +0.5.1 (2017-09-09) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.0. + +* Reverted the extension, back to pure Python (#60) +* Used SQLAlchemy ``RowProxy`` +* Added ``first_or_404`` +* Fixed bug that ``GinoPool`` cannot be inherited + +0.5.0 (2017-09-03) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten + + * Extracted CRUD operations + * Core operations are moved to ``dialect`` and execution context + * Removed ``guess_model``, switched to explicit execution options + * Turned ``timeout`` parameter to an execution option + * Extracted ``pool``, ``connection`` and ``api`` from ``asyncpg_delegate`` +* Added support for SQLAlchemy execution options, and a few custom options +* [Breaking] Made `Model.select` return rows by default (#39) +* Moved `get_or_404` to extensions (#38) +* Added iterator on model classes (#43) +* Added Tornado extension (Contributed by Vladimir Goncharov) +* Added `Model.to_dict` (#47) +* Added an extension module to update `asyncpg.Record` with processed results + + +Early Development Releases +-------------------------- + +Considered as alpha releases. + + +0.4.1 (2017-08-20) +^^^^^^^^^^^^^^^^^^ + +* Support ``select`` on model instance + +0.4.0 (2017-08-15) +^^^^^^^^^^^^^^^^^^ + +* Made ``get_or_404`` more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31) +* Delegated ``sqlalchemy.__all__`` (Contributed by Neal Wang in #10 #33) +* [Breaking] Rewrote JSON/JSONB support (#29) +* Added ``lazy`` parameter on ``db.acquire`` (Contributed by Binghan Li in #32) +* Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34) +* Fixed ``iterate`` API to be compatible with asyncpg (#32) +* Unified exceptions +* [Breaking] Changed ``update`` API (#29) +* Bug fixes + +0.3.0 (2017-08-07) +^^^^^^^^^^^^^^^^^^ + +* Supported ``__table_args__`` (#12) +* Introduced task local to manage connection in context (#19) +* Added ``query.gino`` extension for in-place execution +* Refreshed README (#3) +* Adopted PEP 487 (Contributed by Tony Wang in #17 #27) +* Used ``weakref`` on ``__model__`` of table and query (Contributed by Tony Wang) +* Delegated asyncpg ``timeout`` parameter (Contributed by Neal Wang in #16 #22) + +0.2.3 (2017-08-04) +^^^^^^^^^^^^^^^^^^ + +* Supported any primary key (Contributed by Tony Wang in #11) + +0.2.2 (2017-08-02) +^^^^^^^^^^^^^^^^^^ + +* Supported SQLAlchemy result processor +* Added rich support on JSON/JSONB +* Bug fixes + +0.2.1 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Added ``update`` and ``delete`` API + +0.2.0 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Changed API, no longer reuses asyncpg API + +0.1.1 (2017-07-25) +^^^^^^^^^^^^^^^^^^ + +* Added ``db.bind`` +* API changed: parameter ``conn`` renamed to optional ``bind`` +* Delegated asyncpg Pool with ``db.create_pool`` +* Internal enhancement and bug fixes + +0.1.0 (2017-07-21) +^^^^^^^^^^^^^^^^^^ + +* First release on PyPI. diff --git a/docs/en/1.0/_sources/tutorials.rst.txt b/docs/en/1.0/_sources/tutorials.rst.txt new file mode 100644 index 0000000..6b3bb98 --- /dev/null +++ b/docs/en/1.0/_sources/tutorials.rst.txt @@ -0,0 +1,9 @@ +Tutorials +========= + +.. toctree:: + :glob: + + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* diff --git a/docs/en/1.0/_sources/tutorials/announcement.rst.txt b/docs/en/1.0/_sources/tutorials/announcement.rst.txt new file mode 100644 index 0000000..f159e02 --- /dev/null +++ b/docs/en/1.0/_sources/tutorials/announcement.rst.txt @@ -0,0 +1,297 @@ +官宣:Python 异步编程再添一利器 +=============================== + + GINO 填补了国内外 asyncio ORM 领域的空白 + +随着 Tornado\ [1]_ 和 asyncio\ [2]_ 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。\ +在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +`GINO `__ 了解一下! + +.. image:: ../images/python-gino.webp + :align: center + + +GINO 是谁 +--------- + +GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义\ [3]_\ 手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,\ +你肯定要问一句“这是谁”。 + +ORM,即关系对象映射(Object-Relational Mapping\ [4]_\ ),是一类开发人员喜闻乐见的效率工具,\ +它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢? + +因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性\ +(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),\ +创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:\ +性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 ``current_user.name`` +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试? + +传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,\ +但应用到大型项目中却十分考验开发人员的平均水平。 + +所有这些问题如果再放进异步编程的环境里,那就是 O(n\ :sup:`2`) 的复杂度了——哦不,是 +O(2\ :sup:`n`)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。 + +所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,\ +我索性在 1.0 稳定版发布的前夕做个年终总结。 + + +先说“方便快捷” +-------------- + + “方便快捷”主要说的是开发效率。 + +重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,\ +开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。 + +:: + + from gino import Gino + db = Gino() + class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + +这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟? + +没有错,这就是 SQLAlchemy ORM\ [5]_ 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core\ [6]_\ (SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道\ +(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic\ [7]_\ 、各种 SQLAlchemy 的增强插件\ [8]_\ 、专业领域的 PostGIS/geoalchemy\ [9]_\ +等,GINO 全都兼容。 + +是不是十分方便、十分快捷?不止这样。 + +GINO 一站式地解决了常用 CRUD 快捷方式\ [10]_\ 、上下文管理(aiocontextvars\ [11]_\ [12]_\ )、 +数据库事务封装和嵌套\ [13]_\ 、连接池管理和懒加载\ [14]_\ 等多项便捷功能,无额外依赖关系,即装即用。 + +:: + + daisy = await User.create(name="daisy") + await daisy.update(name="Daisy").apply() + +GINO 还提供了\ [15]_\ 各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado\ [1]_\ 、\ +aiohttp\ [16]_\ 、Sanic\ [17]_\ 、FastAPI\ [18]_\ /Starlette\ [19]_\ 、Quart\ [20]_\ +什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。 + +为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa\ [21]_ 的降维打击: + +1. 最少侵入型\ [22]_\ :SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。 +2. 终身不婚型\ [23]_\ :天生厌恶“对象”,只愿定义“表”,空手接 SQL。 +3. 火力全开型\ [24]_\ :最大程度的便利,非典型异步 ORM。 + +最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、\ +一秒可读百万行的 asyncpg\ [25]_\ ,以及 uvloop\ [26]_\ (可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,\ +深受俄罗斯和乌克兰人民的爱戴。 + + +再说“简单明了” +-------------- + + Explicit is better than implicit. + + Simple is better than complex. + + –The Zen of Python, PEP 20\ [27]_ + +Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,\ +因此 GINO 的很多设计都受到了明确性的影响。 + +比如说,GINO 的 ``Model`` 是完全无状态的普通 Python 对象(POPO\ [28]_\ —— 例如前面的 ``User`` +类,它的实例 ``daisy`` 就是内存里面的常规对象,你可以用 ``daisy.name`` 访问属性,也可以用 +``daisy.name = "DAISY"`` 来修改属性,或者用 ``u = User()`` 来创建新的实例,\ +这些操作都不会访问数据库,绝对绿色环保无毒副作用。 + +等到需要操作数据库的时候,你一定会有感知的。比如执行 ``INSERT`` 要用 +``u = await User.create()``\ ,而 ``UPDATE`` 则是 +``await u.update(name="Daisy").apply()``\ 。 + +.. hint:: + + 其中,``u.update(name="Daisy")`` 与 ``u.name = "Daisy"`` 类似,\ + 都是只在内存里修改对象的属性,不同的是 ``u.update()`` 还会返回一个包含本次变更的中间结果,\ + 对其执行 ``await xxx.apply()`` 则会将这些变更应用到数据库里。 + +这里的 ``await`` 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 ``await`` +就没有数据库操作。 + +另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders\ [29]_\ 。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 ``u = await User.get(1)`` 可以直接获取到 ID 为 ``1`` 的用户对象。\ +但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。\ +加载器的用法也是很简单的,比如一个用户可能写了很多本书: + +:: + + class Book(db.Model): + __tablename__ = "books" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + author_id = db.ForeignKey("users.id") + +然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者: + +:: + + query = Book.outerjoin(User).select() + loader = Book.load(author=User) + async for book in query.gino.load(loader).iterate(): + print(book.title, "written by", book.author.name) + +很简单的一个外连接查询 ``Book.outerjoin(User)``\ ,配合一个直观的加载器 +``Book.load(author=User)``\ ,就实现了: + +1. 执行 ``SELECT * FROM books LEFT JOIN users ON ...``\ ; +2. 将数据库返回结果的每一行中,属于 ``books`` 的字段加载成一个 ``Book`` 实例; +3. 然后将该行中剩下的属于 ``users`` 的字段加载成一个 ``User`` 实例; +4. 最后将 ``User`` 实例设置到 ``Book`` 实例的 ``author`` 属性上。 + +既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,\ +精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。 + + +优势与不足 +---------- + +随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM\ [30]_\ 、ORM\ [31]_\ (是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。 + +GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候\ +不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、\ +快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,\ +可以谨慎地用于生产环境了。 + +以下是近来统计到的关于 GINO 的应用案例: + +- 笔者工作上开发的一个服务:\ https://github.com/uc-cdis/metadata-service +- 还是笔者自己写的一个工具:\ https://github.com/fantix/aintq +- 一个汇率 API 服务:\ https://exchangeratesapi.io/ + + .. image:: ../images/exchangeratesapi.webp + :align: center + +- 各种 Telegram、Discord 的 Bot。 +- ArchLinux 用户包:\ https://aur.archlinux.org/packages/python-gino/ + + .. image:: ../images/archlinux.webp + :align: center + +- https://github.com/bryanforbes/gino-stubs/ +- 俄语教程:\ https://www.youtube.com/watch?v=WW4rOnfhiQY +- 高性能模板项目:\ https://github.com/leosussan/fastapi-gino-arq-uvicorn +- 还有几个商用的,但没征得同意就不贴出来了。 + +另外,GINO 还贴心地提供了中文文档\ [32]_\ ,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!): + +.. image:: ../images/docs.webp + :align: center + +GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能\ +(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。 + + +建设社会主义 +------------ + +GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 **4888 元**\ 的 PyCharm +专业版全家桶 License 一枚\ [33]_\ (没有发票)。 + +目前急需帮助的有: + +1. 各个 Web 框架插件的维护工作需要多人认领; +2. 更多的例子和文档,以及中文、俄文的翻译; +3. MySQL 的支持。 + +以及下面这些一直需要的帮助: + +1. 用 GINO,找 bug,提建议; +2. 修 bug,做功能,提 PR; +3. 维护社区,回答问题,参与讨论; +4. 最后也是最重要的:\ `去 GitHub 上给 GINO 加一颗星星! + `__ + +.. image:: ../images/happy-hacking.png + :align: center + + +关于作者 +-------- + +87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。\ +小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,\ +期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、\ +新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。\ +业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P + +特别感谢 `Tony Wang `__ 对 GINO 项目的贡献,以及\ `所有人 +`__\ 的贡献。 + + +参考文献 +-------- + +.. [1] Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado. +.. [2] Python Software Foundation. asyncio — 异步 I/O. + https://docs.python.org/zh-cn/3/library/asyncio.html. +.. [3] 维基百科. GNU. https://zh.wikipedia.org/wiki/GNU. +.. [4] 维基百科. 对象关系映射. + `https://zh.wikipedia.org/wiki/对象关系映射 + `__. +.. [5] 维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy. +.. [6] Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. + https://docs.sqlalchemy.org/en/13/core/index.html. +.. [7] Michael Bayer. Welcome to Alembic’s documentation!. + https://alembic.sqlalchemy.org/en/latest/. +.. [8] Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. + https://github.com/dahlia/awesome-sqlalchemy. +.. [9] Ricardo GarciaSilva. Does it have postgis support?. + https://github.com/python-gino/gino/issues/627. +.. [10] Fantix King. GINO 基础教程 - 增删改查. + https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations. +.. [11] Python Software Foundation. contextvars —Context Variables. + https://docs.python.org/3/library/contextvars.html. +.. [12] Fantix King. gino/aiocontextvars.py. + https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py. +.. [13] Fantix King. 数据库事务. + https://python-gino.org/docs/zh/master/how-to/transaction.html. +.. [14] Fantix King. 引擎与连接. + https://python-gino.org/docs/zh/master/explanation/engine.html. +.. [15] GINO Community. GINO Community. https://github.com/python-gino/. +.. [16] aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/. +.. [17] Sanic Community. SanicFramework. https://sanicframework.org/. +.. [18] Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/. +.. [19] Encode OSS. Starlette. https://www.starlette.io/. +.. [20] Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/. +.. [21] Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. + https://github.com/CanopyTax/asyncpgsa. +.. [22] Fantix King. 表结构定义 -GINO 引擎. + https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine. +.. [23] Fantix King. 表结构定义 - GINO core.  + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core. +.. [24] Fantix King. 表结构定义 - GINO ORM. + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm. +.. [25] MagicStack Inc. + asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. + https://github.com/MagicStack/asyncpg. +.. [26] MagicStack Inc. uvloop -Ultra fast asyncio event loop. + https://github.com/MagicStack/uvloop. +.. [27] Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/. +.. [28] Wikipedia. Plain old Java object. + https://en.wikipedia.org/wiki/Plain_old_Java_object. +.. [29] Fantix King. 加载器与关系. + https://python-gino.org/docs/zh/master/how-to/loaders.html. +.. [30] Andrey Bondar. Tortoise ORM. https://tortoise.github.io/. +.. [31] Encode OSS. ORM - An async ORM. https://github.com/encode/orm. +.. [32] Fantix King. 欢迎来到 GINO 的文档!. + https://python-gino.org/docs/zh/master/index.html. +.. [33] JetBrains s.r.o. Open Source Licenses. + https://www.jetbrains.com/community/opensource/#support. diff --git a/docs/en/1.0/_sources/tutorials/fastapi.rst.txt b/docs/en/1.0/_sources/tutorials/fastapi.rst.txt new file mode 100644 index 0000000..f256254 --- /dev/null +++ b/docs/en/1.0/_sources/tutorials/fastapi.rst.txt @@ -0,0 +1,669 @@ +Build a FastAPI Server +====================== + +In this tutorial, we'll build a production-ready FastAPI_ server together. +The full functional example is available `here +`__. + +Our application stack will look like this: + +.. image:: ../images/gino-fastapi.svg + :align: center + + +.. _FastAPI: https://fastapi.tiangolo.com/ + + +Start a New Project +------------------- + +Instead of pip, let's use the shiny Poetry_ to manage our project. Follow the link to +`install Poetry `_, and create our new +project in an empty directory: + + +.. code-block:: console + + $ mkdir gino-fastapi-demo + $ cd gino-fastapi-demo + $ git init + $ poetry init + +Then follow the Poetry_ guide to finish the initialization - you may say "no" to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains ``gino-fastapi-demo``. + +.. _Poetry: https://python-poetry.org/ + + +Add Dependencies +---------------- + +FastAPI_ is built on top of the Starlette_ framework, so we shall use the `GINO +extension for Starlette `_. Simply run: + +.. code-block:: console + + $ poetry add gino[starlette] + +Then let's add FastAPI_, together with the lightning-fast ASGI_ server Uvicorn_, and +Gunicorn_ as a production application server: + +.. code-block:: console + + $ poetry add fastapi uvicorn gunicorn + +For database migration, we'll use Alembic_. Because it uses normal DB-API_, we need +psycopg_ here too: + +.. code-block:: console + + $ poetry add alembic psycopg2 + +At last, let's add pytest_ in the development environment for testing. We also want to +add the requests_ library to use the Starlette_ |TestClient|_: + +.. code-block:: console + + $ poetry add -D pytest requests + +.. hint:: + + With the steps above, Poetry_ will automatically create a virtualenv_ for you + behind the scene, and all the dependencies are installed there. We will assume + using this for the rest of the tutorial. But you're free to create your own + virtualenv_, and Poetry_ will honor it when it's activated. + +That's all, this is my ``pyproject.toml`` created by Poetry_, yours should look similar: + +.. code-block:: toml + + [tool.poetry] + name = "gino-fastapi-demo" + version = "0.1.0" + description = "" + authors = ["Fantix King "] + + [tool.poetry.dependencies] + python = "^3.8" + gino = {version = "^1.0", extras = ["starlette"]} + fastapi = "^0.54.1" + uvicorn = "^0.11.3" + gunicorn = "^20.0.4" + alembic = "^1.4.2" + psycopg2 = "^2.8.5" + + [tool.poetry.dev-dependencies] + pytest = "^5.4.1" + requests = "^2.23.0" + + [build-system] + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" + +.. image:: ../images/gino-fastapi-poetry.svg + :align: right + +And there's also an auto-generated ``poetry.lock`` file with the frozen versions. The +directory layout should look like the diagram on the right. Now let's add the two files +to the Git repository (we will skip showing these git operations in future steps): + +.. code-block:: console + + $ git add pyproject.toml poetry.lock + $ git commit -m 'add project dependencies' + +.. _Starlette: https://www.starlette.io/ +.. _ASGI: https://asgi.readthedocs.io/ +.. _Uvicorn: https://www.uvicorn.org/ +.. _Gunicorn: https://gunicorn.org/ +.. _Alembic: https://alembic.sqlalchemy.org/ +.. _DB-API: https://www.python.org/dev/peps/pep-0249/ +.. _psycopg: https://www.psycopg.org/ +.. _pytest: https://docs.pytest.org/ +.. _virtualenv: https://virtualenv.pypa.io/ +.. _requests: https://requests.readthedocs.io/ + + +Write a Simple Server +--------------------- + +Now let's write some Python code. + +We'll create an extra ``src`` directory to include all the Python files, as demonstrated +in the diagram below. This is known as the "`src layout +`_" providing a cleaner hierarchy. + +.. image:: ../images/gino-fastapi-src.svg + :align: right + +The root Python package of our project is named as ``gino_fastapi_demo``, under which we +will create two Python modules: + +* ``asgi`` as the ASGI entry point - we'll feed it to the ASGI server +* ``main`` to initialize our server + +Here's ``main.py``:: + + from fastapi import FastAPI + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + return app + +And we'll simply instantiate our application in ``asgi.py``:: + + from .main import get_app + + app = get_app() + +Then run ``poetry install`` to link our Python package into the ``PYTHONPATH`` in +development mode. We'll be able to start a Uvicorn development server after that: + +.. code-block:: console + + $ poetry install + Installing dependencies from lock file + + No dependencies to install or update + + - Installing gino-fastapi-demo (0.1.0) + + $ poetry run uvicorn gino_fastapi_demo.asgi:app --reload + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + INFO: Started reloader process [53010] + INFO: Started server process [53015] + INFO: Waiting for application startup. + INFO: Application startup complete. + +The ``--reload`` option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server. + +.. hint:: + + As mentioned previously, if you're in your own virtualenv, the command ``poetry run + uvicorn`` can be simplified as just ``uvicorn``. + + ``poetry run`` is a convenient shortcut to run the following command in the + virtualenv managed by Poetry. + + +Add GINO Extension +------------------ + +.. image:: ../images/gino-fastapi-config.svg + :align: right + +Now let's add GINO to our server. + +First of all, we need a way to configure the database. In this tutorial, we'll use the +`configuration system `_ from Starlette_. +Add ``src/gino_fastapi_demo/config.py`` as follows:: + + from sqlalchemy.engine.url import URL, make_url + from starlette.config import Config + from starlette.datastructures import Secret + + config = Config(".env") + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + DB_DSN = config( + "DB_DSN", + cast=make_url, + default=URL( + drivername=DB_DRIVER, + username=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + database=DB_DATABASE, + ), + ) + DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1) + DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16) + DB_ECHO = config("DB_ECHO", cast=bool, default=False) + DB_SSL = config("DB_SSL", default=None) + DB_USE_CONNECTION_FOR_REQUEST = config( + "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True + ) + DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1) + DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1) + +This config file will load from environment variable first, if not found then from a +file named ``.env`` from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this: + +.. code-block:: console + + $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload + +.. image:: ../images/gino-fastapi-env.svg + :align: right + +Or set them in the file ``.env`` (this file must not be committed into Git, remember to +add it to ``.gitignore``): + +.. code-block:: bash + + DB_HOST=localhost + DB_USER=postgres + +Now it's time to create a PostgreSQL_ database and set the connection variables +correctly here. This is usually something like ``createdb yourdbname``, but it may vary +across different platforms, so we won't cover this part in this tutorial. + +.. tip:: + + Alternatively, you could also set ``DB_DSN`` to for example + ``postgresql://user:password@localhost:5432/dbname`` to override the other individual + config values like ``DB_HOST`` defined before ``DB_DSN``. + + If defined, ``DB_DSN`` always have the higher priority over the individual ones, + regardless of where they are defined - even if ``DB_HOST`` is defined in environment + variable and ``DB_DSN`` is defined in ``.env`` file, ``DB_HOST`` is still ignored. + Default value doesn't count. + +.. image:: ../images/gino-fastapi-models.svg + :align: right + +Then, create a new Python sub-package ``gino_fastapi_demo.models`` to encapsulate +database-related code, and add the code below to +``src/gino_fastapi_demo/models/__init__.py``:: + + from gino.ext.starlette import Gino + + from .. import config + + db = Gino( + dsn=config.DB_DSN, + pool_min_size=config.DB_POOL_MIN_SIZE, + pool_max_size=config.DB_POOL_MAX_SIZE, + echo=config.DB_ECHO, + ssl=config.DB_SSL, + use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST, + retry_limit=config.DB_RETRY_LIMIT, + retry_interval=config.DB_RETRY_INTERVAL, + ) + +At last, modify ``src/gino_fastapi_demo/main.py`` to install the GINO extension: + +.. code-block:: diff + + from fastapi import FastAPI + + + +from .models import db + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + + db.init_app(app) + return app + +Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database: + +.. code-block:: console + + WARNING: Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading... + INFO: Shutting down + INFO: Waiting for application shutdown. + INFO: Application shutdown complete. + INFO: Finished server process [63562] + INFO: Started server process [63563] + INFO: Waiting for application startup. + INFO: Connecting to the database: postgresql://fantix:***@localhost + INFO: Database connection pool created: + INFO: Application startup complete. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Create Models and API +--------------------- + +.. image:: ../images/gino-fastapi-models-users.svg + :align: right + +It's time to implement the API now. Let's say we are building a user management service, +through which we could add users, list users and delete users. + +First of all, we need a database table ``users`` to store the data, mapped to a GINO +model named ``User``. We shall add the model in ``gino_fastapi_demo.models.users``:: + + from . import db + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode(), default="unnamed") + +.. image:: ../images/gino-fastapi-views.svg + :align: right + +The model definition is simple enough to explain itself. + +Then we only have to use it properly in the API implementation, for which we'll create a +new Python sub-package ``gino_fastapi_demo.views``, and a new module +``gino_fastapi_demo.views.users`` as follows:: + + from fastapi import APIRouter + from pydantic import BaseModel + + from ..models.users import User + + router = APIRouter() + + @router.get("/users/{uid}") + async def get_user(uid: int): + user = await User.get_or_404(uid) + return user.to_dict() + + class UserModel(BaseModel): + name: str + + @router.post("/users") + async def add_user(user: UserModel): + rv = await User.create(nickname=user.name) + return rv.to_dict() + + @router.delete("/users/{uid}") + async def delete_user(uid: int): + user = await User.get_or_404(uid) + await user.delete() + return dict(id=uid) + + def init_app(app): + app.include_router(router) + +The |APIRouter|_ holds our new APIs locally, and ``init_app`` is used to integrate it +into our FastAPI application. Here we want some `inversion of control`_: let's make the +APIs plugable, so that we don't have to import all possible future views manually. We +shall use the `Entry Points`_ feature to load the dependencies. Add this code below to +``gino_fastapi_demo.main``:: + + import logging + from importlib.metadata import entry_points + + logger = logging.getLogger(__name__) + + def load_modules(app=None): + for ep in entry_points()["gino_fastapi_demo.modules"]: + logger.info("Loading module: %s", ep.name) + mod = ep.load() + if app: + init_app = getattr(mod, "init_app", None) + if init_app: + init_app(app) + +.. hint:: + + If you're running Python < 3.8, you'll need this `importlib-metadata backport + `_. + +And call it in our application factory: + +.. code-block:: diff + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + db.init_app(app) + + load_modules(app) + return app + +Finally, define the entry points in ``pyproject.toml`` following the `Poetry document +for plugins `_: + +.. code-block:: toml + + [tool.poetry.plugins."gino_fastapi_demo.modules"] + "users" = "gino_fastapi_demo.views.users" + +Run ``poetry install`` again to activate the entry points - you may need to restart the +Uvicorn_ development server manually, as the reloader cannot capture the changes we made +to ``pyproject.toml``. + +Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven't created the database tables. + +.. |APIRouter| replace:: ``APIRouter`` +.. _APIRouter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control +.. _Entry Points: https://docs.python.org/3/library/importlib.metadata.html#entry-points + + +Integrate with Alembic +---------------------- + +To get started with Alembic_, run this command in the project root directory: + +.. code-block:: console + + $ poetry run alembic init migrations + +.. image:: ../images/gino-fastapi-alembic.svg + :align: right + +This will generate a new directory ``migrations`` where Alembic_ will store database +migration revisions. At the same time, an ``alembic.ini`` file is created in the project +root directory. Let's simply add all of them to Git control. + +For Alembic_ to use our data models defined with GINO (and of course the database +config), we need to modify ``migrations/env.py`` to connect with the GINO instance: + +.. code-block:: diff + + # add your model's MetaData object here + # for 'autogenerate' support + # from myapp import mymodel + # target_metadata = mymodel.Base.metadata + -target_metadata = None + +from gino_fastapi_demo.config import DB_DSN + +from gino_fastapi_demo.main import db, load_modules + + + +load_modules() + +config.set_main_option("sqlalchemy.url", str(DB_DSN)) + +target_metadata = db + +Then create our first migration revision with: + +.. code-block:: console + + $ poetry run alembic revision --autogenerate -m 'add users table' + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.autogenerate.compare] Detected added table 'users' + Generating migrations/versions/32c0feba61ea_add_users_table.py ... done + +The generated revision file should roughly look like this:: + + def upgrade(): + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("nickname", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + def downgrade(): + op.drop_table("users") + +.. hint:: + + Whenever there is a change to the database schema in the future, just modify the + GINO models and run ``alembic revision --autogenerate`` again to generate new + revisions to track the change. Remember to review the revision file - you may want + to adjust it. + +Eventually, let's apply this migration, by upgrading to the latest revision: + +.. code-block:: console + + $ poetry run alembic upgrade head + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.runtime.migration] Running upgrade -> 32c0feba61ea, add users table + +Now all the APIs should be fully operational, try with the Swagger UI. + + +Write the Tests +--------------- + +In order not to break our development database with running tests, let's create a +separate database to run tests. Apply this change to ``gino_fastapi_demo.config``: + +.. code-block:: diff + + config = Config(".env") + + +TESTING = config("TESTING", cast=bool, default=False) + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + +if TESTING: + + if DB_DATABASE: + + DB_DATABASE += "_test" + + else: + + DB_DATABASE = "gino_fastapi_demo_test" + DB_DSN = config( + +.. hint:: + + You need to run ``createdb`` to actually create the database. If you have set + ``DB_DATABASE`` in ``.env`` - e.g. ``DB_DATABASE=mydb``, the name of the testing + database should be ``mydb_test``. Or else, ``gino_fastapi_demo_test``. + +Then, let's create our pytest_ fixture in ``tests/conftest.py``:: + + import pytest + from alembic.config import main + from starlette.config import environ + from starlette.testclient import TestClient + + environ["TESTING"] = "TRUE" + + @pytest.fixture + def client(): + from gino_fastapi_demo.main import db, get_app + + main(["--raiseerr", "upgrade", "head"]) + + with TestClient(get_app()) as client: + yield client + + main(["--raiseerr", "downgrade", "base"]) + +.. image:: ../images/gino-fastapi-tests.svg + :align: right + +This fixture creates all the database tables before running the test, yield a Starlette_ +|TestClient|_, and drop all the tables with all the data after the test to maintain a +clean environment for the next test. + +Here's a sample test in ``tests/test_users.py``:: + + import uuid + + def test_crud(client): + # create + nickname = str(uuid.uuid4()) + r = client.post("/users", json=dict(name=nickname)) + r.raise_for_status() + + # retrieve + url = f"/users/{r.json()['id']}" + assert client.get(url).json()["nickname"] == nickname + + # delete + client.delete(url).raise_for_status() + assert client.get(url).status_code == 404 + +Then run the test: + +.. code-block:: console + + $ poetry run pytest + =========================== test session starts =========================== + platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 + rootdir: gino-fastapi-demo + collected 1 item + + tests/test_users.py . [100%] + + ============================ 1 passed in 1.21s ============================ + +.. |TestClient| replace:: ``TestClient`` +.. _TestClient: https://www.starlette.io/testclient/ + + +Notes for Production +-------------------- + +Given the popularity of Docker/Kubernetes, we'll build a ``Dockerfile`` for our demo: + +.. code-block:: dockerfile + + FROM python:3.8-alpine as base + + FROM base as builder + RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev + RUN pip install poetry + COPY . /src/ + WORKDIR /src + RUN python -m venv /env && . /env/bin/activate && poetry install + + FROM base + RUN apk add --no-cache postgresql-libs + COPY --from=builder /env /env + COPY --from=builder /src /src + WORKDIR /src + CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"] + +In this ``Dockerfile``, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn_ with +|UvicornWorker|_ from Uvicorn_ as the worker class for best production reliability. + +Let's review what we have in the project. + +.. image:: ../images/gino-fastapi-layout.svg + :align: center + +This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live: + +* Set ``DB_RETRY_LIMIT`` to a larger number to allow staring the application server + before the database is fully ready. +* Implement the same retry logic in ``migrations/env.py`` so that Alembic_ gets the same + functionality. +* Enable ``DB_SSL`` if needed. +* Write a ``docker-compose.yml`` for other developers to get a quick taste or even use + it for development. +* Enable CI_, install ``pytest-cov`` and use ``--cov-fail-under`` to guarantee coverage. +* Integrate static code analysis tools and security/CVE checking tools. +* Automate Alembic_ upgrade properly - e.g. after new version is deployed. +* Be aware of the common security attacks like CSRF_, XSS_, etc. +* Write load tests. + +Again, the source code of the demo is available `here +`__, +and the source of this tutorial is `here +`__. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking! + +.. |UvicornWorker| replace:: ``UvicornWorker`` +.. _UvicornWorker: https://www.uvicorn.org/deployment/#gunicorn +.. _CI: https://en.wikipedia.org/wiki/Continuous_integration +.. _CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/docs/en/1.0/_sources/tutorials/tutorial.rst.txt b/docs/en/1.0/_sources/tutorials/tutorial.rst.txt new file mode 100644 index 0000000..d9527f3 --- /dev/null +++ b/docs/en/1.0/_sources/tutorials/tutorial.rst.txt @@ -0,0 +1,436 @@ +GINO Basics +=========== + +This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of: + +* RDBMS, especially PostgreSQL_ +* `Asynchronous programming in Python `_ + +Knowledge of SQLAlchemy_ is not required. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Introduction +------------ + +Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API. + +You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won't make your +code run faster, if not slower. Please read :doc:`/explanation/why` for more +information. + + +Installation +------------ + +To install GINO, run this command in your terminal: + +.. code-block:: console + + $ pip install gino + +This is the preferred method to install GINO, as it will always install the +most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +Alternatively, if you are using Poetry_ to manage your project dependencies, +you may want to run: + +.. code-block:: console + + $ poetry add gino + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ +.. _Poetry: https://python-poetry.org + + +Declare Models +-------------- + +First of all, we'll need a :class:`~gino.api.Gino` object, usually under the +name of ``db`` as a global variable:: + + from gino import Gino + + db = Gino() + +``db`` acts like a reference to the database, most database interactions will +go through it. + +"Model" is a basic concept in GINO, it is a Python class inherited from +:attr:`db.Model `. Each :class:`~gino.declarative.Model` +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let's declare a model:: + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + +By declaring this ``User`` class, we are actually defining a database table +named ``users``, with two columns ``id`` and ``nickname``. Note that the fixed +:attr:`~gino.declarative.Model.__tablename__` property is required. GINO +suggests singular for model names, and plural for table names. Each +:class:`db.Column ` property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to ``db`` types `here +`__ in the SQLAlchemy +documentation. + +.. note:: + + SQLAlchemy_ is a powerful ORM library for non-asynchronous programming in + Python, on top of which GINO is built. SQLAlchemy supports many popular + RDBMS including PostgreSQL and MySQL through different dialect + implementation, so that the same Python code can be compiled into different + SQL depending on the dialect you choose. GINO inherited this support too, + but for now there is only one dialect for PostgreSQL through asyncpg_. + +.. _asyncpg: https://github.com/MagicStack/asyncpg +.. _SQLAlchemy: https://www.sqlalchemy.org/ + +If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:: + + class Booking(db.Model): + __tablename__ = 'bookings' + + day = db.Column(db.Date) + booker = db.Column(db.String) + room = db.Column(db.String) + + _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey') + _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True) + _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room') + +It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +`here `__ in the +SQLAlchemy documentation. + +Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the :attr:`~gino.declarative.Model.__table_args__` attribute. In order +to e.g. define constraints in mixin classes, +:func:`~gino.declarative.declared_attr` is required. Please feel free to read +more about it in its API documentation. + + +Get Connected +------------- + +The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let's create a +PostgreSQL database for this tutorial: + +.. code-block:: console + + $ createdb gino + +Then we tell our ``db`` object to connect to this database:: + + import asyncio + + async def main(): + await db.set_bind('postgresql://localhost/gino') + + asyncio.get_event_loop().run_until_complete(main()) + +If this runs successfully, then you are connected to the newly created database. +Here ``postgresql`` indicates the database dialect to use (the default driver +is ``asyncpg``, you can explicitly specify that with ``postgresql+asyncpg://``, +or simply ``asyncpg://``), ``localhost`` is where the server is, and ``gino`` +is the name of the database. Check `here +`__ for more +information about how to compose this database URL. + +.. note:: + + Under the hood :meth:`~gino.api.Gino.set_bind` calls + :func:`~gino.create_engine` and bind the engine to this ``db`` object. GINO + engine is similar to SQLAlchemy engine, but not identical. Because GINO + engine is asynchronous, while the other is not. Please refer to the API + reference of GINO for more information. + +Now that we are connected, let's create the table in database (in the same +``main()`` method):: + + await db.gino.create_all() + +.. warning:: + + It is :meth:`db.gino.create_all `, + not :meth:`db.create_all `, because + ``db`` is inherited from SQLAlchemy :class:`~sqlalchemy.schema.MetaData`, + and :meth:`db.create_all ` is from + SQLAlchemy using non-asynchronous methods, which doesn't work with the + bound GINO engine. + + In practice :meth:`~gino.schema.GinoSchemaVisitor.create_all` is usually + not an ideal solution. To manage database schema, tool like Alembic_ is + recommended, please see how to :doc:`/how-to/alembic`. + +If you want to explicitly disconnect from the database, you can do this:: + + await db.pop_bind().close() + +Let's review the code we have so far together in one piece before moving on:: + + import asyncio + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + + async def main(): + await db.set_bind('postgresql://localhost/gino') + await db.gino.create_all() + + # further code goes here + + await db.pop_bind().close() + + + asyncio.get_event_loop().run_until_complete(main()) + +.. _Alembic: https://bitbucket.org/zzzeek/alembic + + +CRUD Operations +--------------- + +In order to operate on the database, one of GINO's core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations. + + +Create +^^^^^^ + +Let's start by creating a ``User``:: + + user = await User.create(nickname='fantix') + # This will cause GINO to execute this SQL with parameter 'fantix': + # INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname + +As mentioned previously, ``user`` object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:: + + print(f'ID: {user.id}') # 1 + print(f'Nickname: {user.nickname}') # fantix + +It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:: + + user = User(nickname='fantix') + user.nickname += ' (founder)' + await user.create() + +Retrieve +^^^^^^^^ + +To retrieve a model object from database by primary key, you can use the class +method :meth:`~gino.crud.CRUDModel.get` on the model class. Now let's retrieve +the same row:: + + user = await User.get(1) + # SQL (parameter: 1): + # SELECT users.id, users.nickname FROM users WHERE users.id = $1 + +Normal SQL queries are done through a class property +:attr:`~gino.crud.CRUDModel.query`. For example, let's retrieve all ``User`` +objects from database as a list:: + + all_users = await db.all(User.query) + # SQL: + # SELECT users.id, users.nickname FROM users + +Alternatively, you can use the ``gino`` extension on +:attr:`~gino.crud.CRUDModel.query`. This has exactly the same effect as above:: + + all_users = await User.query.gino.all() + # SQL: + # SELECT users.id, users.nickname FROM users + +.. note:: + + ``User.query`` is actually a SQLAlchemy query, with its own + non-asynchronous execution methods. GINO added this ``gino`` extension on + all executable SQLAlchemy clause objects to conveniently execute them in + the asynchronous way, so that it is even not needed to import the ``db`` + reference for execution. + +Now let's add some filters. For example, find all users with ID lower than 10:: + + founding_users = await User.query.where(User.id < 10).gino.all() + # SQL (parameter: 10): + # SELECT users.id, users.nickname FROM users WHERE users.id < $1 + +Read more `here `__ +about writing queries, because the query object is exactly from SQLAlchemy core. + +.. warning:: + + Once you get a model object, it is purely in memory and fully detached from + the database. That means, if the row is externally updated, the object + values remain unchanged. Likewise, changes made to the object won't affect + the database values. + + Also, GINO keeps no track of model objects, therefore getting the same row + twice returns two different object with identical values. Modifying one + does not magically affect the other one. + + Different than traditional ORMs, the GINO model objects are more like + objective SQL results, rather than stateful ORM objects. In order to adapt + for asynchronous programming, GINO is designed to be that simple. That's + also why GINO Is Not ORM. + +Sometimes we want to get only one object, for example getting the user by name +when logging in. There's a shortcut for this scenario:: + + user = await User.query.where(User.nickname == 'fantix').gino.first() + # SQL (parameter: 'fantix'): + # SELECT users.id, users.nickname FROM users WHERE users.nickname = $1 + +If there is no user named "fantix" in database, ``user`` will be ``None``. + +And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +:meth:`~gino.crud.CRUDModel.select` class method:: + + name = await User.select('nickname').where(User.id == 1).gino.scalar() + # SQL (parameter: 1): + # SELECT users.nickname FROM users WHERE users.id = $1 + print(name) # fantix + +Or get the count of all users:: + + population = await db.func.count(User.id).gino.scalar() + # SQL: + # SELECT count(users.id) AS count_1 FROM users + print(population) # 17 for example + + +Update +^^^^^^ + +Then let's try to make some modifications. In this example we'll mixin some +retrieve operations we just tried. :: + + # create a new user + user = await User.create(nickname='fantix') + + # get its name + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + assert name == user.nickname # they are both 'fantix' before the update + + # modification here + await user.update(nickname='daisy').apply() + # SQL (parameters: 'daisy', 1): + # UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname + print(user.nickname) # daisy + + # get its name again + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + print(name) # daisy + assert name == user.nickname # they are both 'daisy' after the update + +So :meth:`~gino.crud.CRUDModel.update` is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +:meth:`~gino.crud.UpdateRequest.apply` call makes the update happen in database. + +.. note:: + + GINO explicitly split the in-memory update and SQL update into two methods: + :meth:`~gino.crud.CRUDModel.update` and + :meth:`~gino.crud.UpdateRequest.apply`. :meth:`~gino.crud.CRUDModel.update` + will update the in-memory model object and return an + :class:`~gino.crud.UpdateRequest` object which contains all the + modifications. A following :meth:`~gino.crud.UpdateRequest.apply` on + :class:`~gino.crud.UpdateRequest` object will apply these recorded + modifications to database by executing a compiled SQL. + +.. tip:: + + :class:`~gino.crud.UpdateRequest` object has another method named + :meth:`~gino.crud.UpdateRequest.update` which works the same as the one + on model object, just that it combines the new modifications together with + the ones already recorded in current :class:`~gino.crud.UpdateRequest` + object, and it returns the same :class:`~gino.crud.UpdateRequest` object. + That means, you can chain the updates and end up with one + :meth:`~gino.crud.UpdateRequest.apply`, or make use of the + :class:`~gino.crud.UpdateRequest` object to combine several updates in a + batch. + +:meth:`~gino.crud.CRUDModel.update` on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the :meth:`~gino.crud.CRUDModel.update` on model class level, with a +bit difference:: + + await User.update.values(nickname='Founding Member ' + User.nickname).where( + User.id < 10).gino.status() + # SQL (parameter: 'Founding Member ', 10): + # UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2 + + name = await User.select('nickname').where( + User.id == 1).gino.scalar() + print(name) # Founding Member fantix + +There is no :class:`~gino.crud.UpdateRequest` here, everything is again +SQLAlchemy clause, its +`documentation `_ here for +your reference. + + +Delete +^^^^^^ + +At last. Deleting is similar to updating, but way simpler. :: + + + user = await User.create(nickname='fantix') + await user.delete() + # SQL (parameter: 1): + # DELETE FROM users WHERE users.id = $1 + print(await User.get(user.id)) # None + +.. hint:: + + Remember the model object is in memory? In the last :func:`print` + statement, even though the row is already deleted in database, the object + ``user`` still exists with its values untouched. + +Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):: + + await User.delete.where(User.id > 10).gino.status() + # SQL (parameter: 10): + # DELETE FROM users WHERE users.id > $1 + + +With basic :doc:`/how-to/crud`, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking! diff --git a/docs/en/1.0/_static/css/gino.css b/docs/en/1.0/_static/css/gino.css new file mode 100644 index 0000000..c26ddbc --- /dev/null +++ b/docs/en/1.0/_static/css/gino.css @@ -0,0 +1,806 @@ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: 100%; + box-sizing: border-box; + font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; +} + +body { + background-color: #F0F0F0; + font-weight: 500; +} + +a { + color: #3E6CDE; +} + +code, kbd, samp { + font-family: monospace; + background-color: #f8f8f8; + border: 1px solid #f0f0f0; + padding: 0 4px; + border-radius: 2px; +} + +strong { + font-weight: 700; +} + +:root { + --v-primary-base: #212B73; + --v-secondary-base: #AD755C; + --v-accent-base: #EEF5FF; + --v-error: #FF5252; + --v-info: #BDC8F1; + --v-info-light: #3E6CDE; + --v-success: #4CAF50; + --v-success-light: rgba(76, 175, 80, 0.2); + --v-warning: #FFC107; + --v-warning-light: rgba(255, 193, 7, 0.2); + --v-border: #444b54; + --v-border-light: #c0cee1; +} + +nav { + background-color: rgba(33, 43, 115, 0.95); + -webkit-box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); +} + +.nav-wrapper { + padding: 4px 16px; + display: flex; + align-items: center; +} + +nav .brand-logo { + position: relative; + display: flex; + align-items: center; + left: 0; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +.breadcrumbs { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + -ms-overflow-style: none; +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs .breadcrumb, +.breadcrumbs .breadcrumb::before { + content: '>'; + font-size: 16px; + white-space: nowrap; +} + + +.btn-flat { + display: flex; + align-items: center; + font-weight: 500; + margin-left: 22px; + text-decoration: none; + color: #fff; + padding: 14px; + font-size: 14px; + -webkit-transition: .2s; + transition: .2s; +} + +.btn-flat:hover { + color: #f8d230; + text-shadow: #f8d230 0 0 5px; +} + +a.brackets:before, +span.brackets:empty, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +a.footnote-reference { + vertical-align: super; + font-size: 0.8em; +} + +.footnote dt { + float: left; +} + +div.search { + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +} + +div.search i { + position: absolute; + left: 10px; + transition: 0.3s; +} + +#search:focus + i { + color: var(--v-primary-base); +} + +#search { + flex: 0.1 1 192px; + background-color: rgba(255, 255, 255, 0.16); + border-radius: 28px; + height: 38px; + padding: 0 32px 0 42px; + margin: 0; + border: none; + transition: 0.3s; + color: rgba(255, 255, 255, 0.32); + font-size: 14px; +} + +#search:focus { + border: none; + background-color: #FFFFFF; + color: #000000; +} + +#search-results { + position: absolute; + left: 0; + right: -250px; + max-height: 61.8vh; + background-color: rgba(0, 0, 0, 0.8); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + overflow: scroll; + top: 51px; + padding: 16px; +} + +#search-results li { + float: none; + line-height: 1.5; + font-size: 14px; +} + +#search-results li a { + color: #4E7CEE; + padding: 0; +} + +#search-results li .context { + padding: 0 0 24px 24px; +} +#search-results .highlighted { + color: #f8d230; +} + +.theme-dark { + color: #FFFFFF; +} + +.spacer { + flex-grow: 1; +} + +.img { + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.sidenav { + top: 64px; + z-index: 900; + box-shadow: none; + bottom: 0; + height: auto; +} + +.col.body { + padding: 0; +} + +#main-content { + padding: 0 24px; + background-color: white; +} + +.sidenav ul li:first-of-type { + display: none; +} + +.sidenav ul ul li:first-of-type { + display: block; +} + +.sidenav .caption { + font-size: 1.2em; + color: var(--v-primary-base); + padding: 16px 32px 0 48px; + position: relative; + font-weight: 600; +} + +.sidenav .caption::before { + position: absolute; + display: block; + content: " "; + width: 20px; + height: 20px; + left: 16px; + top: 20px; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.sidenav li > a { + font-size: 14px; + line-height: 14px; + padding: 12px 24px 12px 48px; + height: auto; +} + +.sidenav li li > a { + padding-left: 70px; +} + +.sidenav .caption:nth-of-type(1)::before { + background-image: url(../images/tutorials-icon.svg); +} +.sidenav .caption:nth-of-type(2)::before { + background-image: url(../images/how-to-icon.svg); +} +.sidenav .caption:nth-of-type(3)::before { + background-image: url(../images/explanation-logo.svg); +} +.sidenav .caption:nth-of-type(4)::before { + background-image: url(../images/reference-logo.svg); +} + +.sidenav a.current { + background-color: var(--v-accent-base); +} + +.github { + margin-left: 16px; + transition: 0.3s; +} + +.github:hover { + text-shadow: #FFFFFF 0 0 5px; +} + +.github i { + font-size: 40px; +} + +.right-nav.col { + padding: 0; +} + +.table-of-contents { + padding: 16px; + top: 64px; + bottom: 0; + overflow-y: auto; +} + +.table-of-contents li { + padding: 0; +} + +.table-of-contents > ul > li > a { + display: none; +} + +.table-of-contents > ul ul > li > a.active, +.table-of-contents > ul ul > li > a:hover { + padding-left: 14px; +} + +.table-of-contents > ul ul ul > li > a.active, +.table-of-contents > ul ul ul > li > a:hover { + padding-left: 30px; +} + +.table-of-contents > ul ul ul > li > a { + padding-left: 32px; +} + +.table-of-contents > ul ul ul ul > li > a.active, +.table-of-contents > ul ul ul ul > li > a:hover { + padding-left: 46px; +} + +.table-of-contents > ul ul ul ul > li > a { + padding-left: 48px; +} + +.table-of-contents a { + height: auto; + padding-top: 2px; + padding-bottom: 2px; +} + +.table-of-contents a.active, +.table-of-contents a:hover { + color: var(--v-primary-base); + border-left: 2px solid var(--v-primary-base); + font-weight: 500; +} + +.table-of-contents #version-selector hr { + margin-top: 48px; +} + +.table-of-contents #version-selector div { + margin-top: 16px; + color: #757575; +} + +.table-of-contents #version-selector a { + color: #757575; +} + +.table-of-contents .add-your-lang, +.table-of-contents .add-your-lang a, +.table-of-contents .add-your-lang a:active, +.table-of-contents .add-your-lang a:hover { + border-left: none; + font-size: 14px; + text-decoration: underline; + text-align: right; + margin-top: 8px; + color: #aaa; + font-weight: normal; +} + +.table-of-contents #version-selector span, +.table-of-contents #version-selector a, +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + border-left: none; + padding: 8px; + margin-left: 16px; +} + +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + color: var(--v-info-light); +} + +.table-of-contents #version-selector span { + color: var(--v-info-light); +} + +h1 { + font-size: 32px; + margin: 32px 0 20px 0; + font-weight: 600; +} + +h2 { + font-size: 26px; + margin: 18px 0 12px 0; + font-weight: 600; +} + +h3 { + font-size: 22px; + margin: 17px 0 11px 0; + font-weight: 600; +} + +h4 { + font-size: 18px; + margin: 14px 0 9px 0; + font-weight: 600; +} + +h5 { + font-size: 16px; + margin: 13px 0 8px 0; + font-weight: 600; +} + +h6 { + font-size: 15px; + margin: 12px 0 7px 0; + font-weight: 600; +} + +a.headerlink { + padding-left: 8px; + visibility: hidden; + position: absolute; + font-family: sans-serif; +} + +:hover > a.headerlink { + visibility: visible; +} + +.section { + /*margin-top: -64px;*/ + /*padding-top: 64px;*/ +} + +.highlight { + background-color: rgba(0, 0, 0, 0.8); + clear: both; +} + +.highlight pre { + border: 1px solid var(--v-border-light); + padding: 1em; + font-size: 14px; + overflow-x: auto; +} + +.body li p { + margin: 0; +} + +.body ul li { + list-style-type: disc; + margin-left: 2rem; +} + +.body ul li li { + list-style-type: circle; +} + +.body ul li li li { + list-style-type: square; +} + +.admonition { + border-radius: 4px; + padding: 1px 1rem; + box-shadow: #D1CECE 0 0 4px; +} + +.admonition-title { + font-size: 18px; + font-weight: 600; + color: #3E6CDE; +} + +.admonition-title:before { + content: " "; + display: inline-block; + width: 20px; + height: 20px; + margin: 0 8px -4px 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.admonition.note, +.admonition.seealso { + background-color: #F5FAFD; +} + +.admonition.note .admonition-title:before, +.admonition.seealso .admonition-title:before { + background-image: url(../images/icon-note.svg); +} + +.admonition.tip, +.admonition.hint { + background-color: #F5FDF6; +} + +.admonition.hint .admonition-title:before { + background-image: url(../images/icon-hint.svg); +} + +.admonition.warning { + background-color: #FDF5F5; +} + +.admonition.warning .admonition-title:before { + background-image: url(../images/icon-warning.svg); +} + +.admonition.tip .admonition-title:before { + background-image: url(../images/icon-info.svg); +} + +.body .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 700px; +} + +.body .section ul.boxed-nav li { + border-radius: 4px; + box-shadow: #D1CECE 0 0 4px; + padding: 10px; + margin: 25px 0 0; + width: 340px; + list-style: none; + color: #202A73; + font-weight: 500; + overflow: hidden; +} + +.body .section ul.boxed-nav li div { + background-color: #F4F7FC; + border-radius: 4px; + height: 110px; + padding: 30px 30px 0; + background-image: url(../images/box-bg-dec.svg); + background-repeat: no-repeat; + background-position: -15px -10px; +} + +.body .section ul.boxed-nav li:nth-of-type(3n) div { + background-image: url(../images/box-bg-dec-2.svg); +} + +.body .section ul.boxed-nav li:nth-of-type(3n+1) div { + background-image: url(../images/box-bg-dec-3.svg); +} + +.body .section ul.boxed-nav li a img { + width: 42px; + margin: 0 30px 30px 0; + float: left; +} + +.body .section ul.boxed-nav li p { + font-size: 18px; + color: #212B73; + line-height: 18px; + margin-bottom: 8px; +} + +.body .section ul.boxed-nav li p:nth-of-type(2) { + font-size: 14px; + word-break: break-word; +} + +.body .section ul.boxed-nav p a, +.body .section ul.boxed-nav p a:visited { + color: #212B73; +} + +p.divio { + position: absolute; + font-size: 0.8em; + color: #aaa; + margin-top: 40px; +} + +p.divio a, p.divio a:visited { + color: #aaa; +} + +.github-stars { + display: flex; + align-items: center; + padding: 0; + margin-left: 32px; + border-radius: 27px; + font-size: 14px; + font-weight: 500; +} + +.github-stars div { + border: 1px solid #fff; + line-height: 27px; + background-color: #1E2B78; + transition: 0.1s; + height: 29px; +} + +.github-stars div.left { + border-bottom-left-radius: 27px; + border-top-left-radius: 27px; + border-right: none; + padding: 0 7px 0 34px; + position: relative; +} + +.github-stars div.left .github-logo { + position: absolute; + height: 29px; + top: -1px; + left: -1px; +} + +.github-stars div.right { + border-bottom-right-radius: 27px; + border-top-right-radius: 27px; + padding: 0 7px 0 7px; + display: flex; + align-items: center; +} + +.github-stars div.right img { + height: 14px; + margin-right: 7px; +} + +.github-stars:hover { + color: white; + text-shadow: none; + box-shadow: #ffffff 0 0 5px; +} + +.btn-large.white img { + width: 32px; + height: 32px; + margin-top: 6px; +} + +.btn-large.white div { + color: #3E6CDE; + position: absolute; + bottom: 0; + width: 56px; + line-height: 18px; + font-size: 14px; + font-weight: 600; +} + +.sidenav-overlay { + z-index: 897; +} + +nav a.sidenav-trigger { + height: 24px; + margin: 16px 16px 16px 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +nav a.sidenav-trigger hr { + width: 24px; + margin: 0; + border: 0; + background-color: #fff; + height: 2px; +} + +.fixed-action-btn { + display: none; +} + +@media (min-width: 993px) { + main { + padding-left: 300px; + } + + #main-content { + padding: 0 48px; + margin: 16px; + border-radius: 4px; + } + + .admonition { + margin: 1.5rem 2rem; + padding: 1px 3rem; + } + + img.align-center { + max-width: 100%; + display: block; + margin: 0 auto; + } + + img.align-left { + max-width: 100%; + float: left; + margin: 0 2rem 1.5rem 0; + } + + img.align-right { + max-width: 100%; + float: right; + margin: 0 0 1.5rem 2rem; + } + + .table-of-contents { + width: calc(25vw - 75px); + } + + p.divio { + margin-left: -47px; + } + + .btn-flat { + margin-left: 22px; + padding: 14px; + } + + .sidenav { + top: 64px; + } + + nav a.sidenav-trigger { + display: none; + } +} + +@media (max-width: 992px) { + img.align-left, + img.align-center, + img.align-right { + display: block; + margin: 1.5rem auto; + max-width: 100%; + } + + .admonition { + overflow: scroll; + } + + #main-content { + padding-bottom: 56px; + } + + #main-content p { + overflow-x: scroll; + } + + main > .row { + margin-bottom: 0; + } + + .breadcrumbs { + display: none; + } + + .btn-flat { + margin-left: 10px; + padding: 5px; + } +} + +@media (max-width: 740px) { + #search-container { + display: none; + } +} + +@media (max-width: 600px) { + .sidenav { + top: 56px; + } + + .fixed-action-btn { + display: block; + } +} + +@media (max-width: 510px) { + .github-stars { + display: none; + } +} diff --git a/docs/en/1.0/_static/css/materialize.min.css b/docs/en/1.0/_static/css/materialize.min.css new file mode 100644 index 0000000..74b1741 --- /dev/null +++ b/docs/en/1.0/_static/css/materialize.min.css @@ -0,0 +1,13 @@ +/*! + * Materialize v1.0.0 (http://materializecss.com) + * Copyright 2014-2017 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/static/favicon.ico b/docs/en/1.0/_static/favicon.ico similarity index 100% rename from static/favicon.ico rename to docs/en/1.0/_static/favicon.ico diff --git a/docs/en/1.0/_static/images/box-bg-dec-2.svg b/docs/en/1.0/_static/images/box-bg-dec-2.svg new file mode 100644 index 0000000..0c1e246 --- /dev/null +++ b/docs/en/1.0/_static/images/box-bg-dec-2.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/en/1.0/_static/images/box-bg-dec-3.svg b/docs/en/1.0/_static/images/box-bg-dec-3.svg new file mode 100644 index 0000000..0d757ca --- /dev/null +++ b/docs/en/1.0/_static/images/box-bg-dec-3.svg @@ -0,0 +1,25 @@ + + + + Path 10 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/1.0/_static/images/box-bg-dec.svg b/docs/en/1.0/_static/images/box-bg-dec.svg new file mode 100644 index 0000000..a6e38d5 --- /dev/null +++ b/docs/en/1.0/_static/images/box-bg-dec.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/en/1.0/_static/images/explanation-logo.svg b/docs/en/1.0/_static/images/explanation-logo.svg new file mode 100644 index 0000000..98df7c3 --- /dev/null +++ b/docs/en/1.0/_static/images/explanation-logo.svg @@ -0,0 +1,20 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/how-to-icon.svg b/docs/en/1.0/_static/images/how-to-icon.svg new file mode 100644 index 0000000..6506741 --- /dev/null +++ b/docs/en/1.0/_static/images/how-to-icon.svg @@ -0,0 +1,24 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/icon-hint.svg b/docs/en/1.0/_static/images/icon-hint.svg new file mode 100644 index 0000000..db86c44 --- /dev/null +++ b/docs/en/1.0/_static/images/icon-hint.svg @@ -0,0 +1,27 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/icon-info.svg b/docs/en/1.0/_static/images/icon-info.svg new file mode 100644 index 0000000..c4690a0 --- /dev/null +++ b/docs/en/1.0/_static/images/icon-info.svg @@ -0,0 +1,27 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/icon-note.svg b/docs/en/1.0/_static/images/icon-note.svg new file mode 100644 index 0000000..03308b0 --- /dev/null +++ b/docs/en/1.0/_static/images/icon-note.svg @@ -0,0 +1,21 @@ + + + + 编组 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/icon-warning.svg b/docs/en/1.0/_static/images/icon-warning.svg new file mode 100644 index 0000000..4e3ea02 --- /dev/null +++ b/docs/en/1.0/_static/images/icon-warning.svg @@ -0,0 +1,29 @@ + + + + 编组 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/language.svg b/docs/en/1.0/_static/images/language.svg new file mode 100644 index 0000000..b61c7d4 --- /dev/null +++ b/docs/en/1.0/_static/images/language.svg @@ -0,0 +1,16 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/reference-logo.svg b/docs/en/1.0/_static/images/reference-logo.svg new file mode 100644 index 0000000..b79d156 --- /dev/null +++ b/docs/en/1.0/_static/images/reference-logo.svg @@ -0,0 +1,22 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/images/tutorials-icon.svg b/docs/en/1.0/_static/images/tutorials-icon.svg new file mode 100644 index 0000000..ff76008 --- /dev/null +++ b/docs/en/1.0/_static/images/tutorials-icon.svg @@ -0,0 +1,21 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/js/documentation_options.js b/docs/en/1.0/_static/js/documentation_options.js new file mode 100644 index 0000000..5c4267a --- /dev/null +++ b/docs/en/1.0/_static/js/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.0.2.dev0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/en/1.0/_static/js/gino.js b/docs/en/1.0/_static/js/gino.js new file mode 100644 index 0000000..1690ada --- /dev/null +++ b/docs/en/1.0/_static/js/gino.js @@ -0,0 +1,642 @@ +$u = _.noConflict(); + +function splitQuery(query) { + return query.split(/\s+/); +} + +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2 +}; + +var Search = { + _index : null, + + setIndex: function(index) { + this._index = index; + }, + + performObjectSearch : function(object, otherterms) { + var filenames = this._index.filenames; + var docnames = this._index.docnames; + var objects = this._index.objects; + var objnames = this._index.objnames; + var titles = this._index.titles; + + var i; + var results = []; + + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + var fullnameLower = fullname.toLowerCase() + if (fullnameLower.indexOf(object) > -1) { + var score = 0; + var parts = fullnameLower.split('.'); + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower == object || parts[parts.length - 1] == object) { + score += Scorer.objNameMatch; + // matches in last name + } else if (parts[parts.length - 1].indexOf(object) > -1) { + score += Scorer.objPartialMatch; + } + var match = objects[prefix][name]; + var objname = objnames[match[1]][2]; + var title = titles[match[0]]; + // If more than one term searched for, we require other words to be + // found in the name/title/description + if (otherterms.length > 0) { + var haystack = (prefix + ' ' + name + ' ' + + objname + ' ' + title).toLowerCase(); + var allfound = true; + for (i = 0; i < otherterms.length; i++) { + if (haystack.indexOf(otherterms[i]) == -1) { + allfound = false; + break; + } + } + if (!allfound) { + continue; + } + } + var descr = objname + (', in ') + title; + + var anchor = match[3]; + if (anchor === '') + anchor = fullname; + else if (anchor == '-') + anchor = objnames[match[1]][1] + '-' + fullname; + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) { + score += Scorer.objPrio[match[2]]; + } else { + score += Scorer.objPrioDefault; + } + results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]); + } + } + } + + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch : function(searchterms, excluded, terms, titleterms) { + var docnames = this._index.docnames; + var filenames = this._index.filenames; + var titles = this._index.titles; + + var i, j, file; + var fileMap = {}; + var scoreMap = {}; + var results = []; + + // perform the search on the required terms + for (i = 0; i < searchterms.length; i++) { + var word = searchterms[i]; + var files = []; + var _o = [ + {files: terms[word], score: Scorer.term}, + {files: titleterms[word], score: Scorer.title} + ]; + // add support for partial matches + if (word.length > 2) { + for (var w in terms) { + if (w.match(word) && !terms[word]) { + _o.push({files: terms[w], score: Scorer.partialTerm}) + } + } + for (var w in titleterms) { + if (w.match(word) && !titleterms[word]) { + _o.push({files: titleterms[w], score: Scorer.partialTitle}) + } + } + } + + // no match but word was a required one + if ($u.every(_o, function(o){return o.files === undefined;})) { + break; + } + // found search word in contents + $u.each(_o, function(o) { + var _files = o.files; + if (_files === undefined) + return + + if (_files.length === undefined) + _files = [_files]; + files = files.concat(_files); + + // set score for the word in each file to Scorer.term + for (j = 0; j < _files.length; j++) { + file = _files[j]; + if (!(file in scoreMap)) + scoreMap[file] = {}; + scoreMap[file][word] = o.score; + } + }); + + // create the mapping + for (j = 0; j < files.length; j++) { + file = files[j]; + if (file in fileMap && fileMap[file].indexOf(word) === -1) + fileMap[file].push(word); + else + fileMap[file] = [word]; + } + } + + // now check if the files don't contain excluded terms + for (file in fileMap) { + var valid = true; + + // check if all requirements are matched + var filteredTermCount = // as search terms with length < 3 are discarded: ignore + searchterms.filter(function(term){return term.length > 2}).length + if ( + fileMap[file].length != searchterms.length && + fileMap[file].length != filteredTermCount + ) continue; + + // ensure that none of the excluded terms is in the search result + for (i = 0; i < excluded.length; i++) { + if (terms[excluded[i]] == file || + titleterms[excluded[i]] == file || + $u.contains(terms[excluded[i]] || [], file) || + $u.contains(titleterms[excluded[i]] || [], file)) { + valid = false; + break; + } + } + + // if we have still a valid result we can add it to the result list + if (valid) { + // select one (max) score for the file. + // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... + var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); + results.push([docnames[file], titles[file], '', null, score, filenames[file]]); + } + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurrence, the + * latter for highlighting it. + */ + makeSearchSummary : function(htmlText, keywords, hlwords) { + var text = Search.htmlToText(htmlText); + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; + }, + + htmlToText : function(htmlString) { + var htmlElement = document.createElement('span'); + htmlElement.innerHTML = htmlString; + $(htmlElement).find('.headerlink').remove(); + docContent = $(htmlElement).find('[role=main]')[0]; + if(docContent === undefined) { + console.warn("Content block not found. Sphinx search tries to obtain it " + + "via '[role=main]'. Could you check your theme or template."); + return ""; + } + return docContent.textContent || docContent.innerText; + }, + + query: function(query) { + this.out = $('#search-results'); + this.out.empty(); + this.output = $(''),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0'),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); \ No newline at end of file diff --git a/docs/en/1.0/_static/js/underscore.js b/docs/en/1.0/_static/js/underscore.js new file mode 100644 index 0000000..5b55f32 --- /dev/null +++ b/docs/en/1.0/_static/js/underscore.js @@ -0,0 +1,31 @@ +// Underscore.js 1.3.1 +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, +h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= +b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== +null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= +function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= +e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= +function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, +c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; +b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, +1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; +b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; +b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), +function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ +u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= +function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= +true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/docs/en/1.0/_static/logo.svg b/docs/en/1.0/_static/logo.svg new file mode 100644 index 0000000..78bb2b4 --- /dev/null +++ b/docs/en/1.0/_static/logo.svg @@ -0,0 +1,42 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/_static/pygments.css b/docs/en/1.0/_static/pygments.css new file mode 100644 index 0000000..c3bec81 --- /dev/null +++ b/docs/en/1.0/_static/pygments.css @@ -0,0 +1,72 @@ +.highlight .hll { background-color: #49483e } +.highlight { background: #272822; color: #f8f8f2 } +.highlight .c { color: #75715e } /* Comment */ +.highlight .err { color: #960050; background-color: #1e0010 } /* Error */ +.highlight .k { color: #66d9ef } /* Keyword */ +.highlight .l { color: #ae81ff } /* Literal */ +.highlight .n { color: #f8f8f2 } /* Name */ +.highlight .o { color: #f92672 } /* Operator */ +.highlight .p { color: #f8f8f2 } /* Punctuation */ +.highlight .ch { color: #75715e } /* Comment.Hashbang */ +.highlight .cm { color: #75715e } /* Comment.Multiline */ +.highlight .cp { color: #75715e } /* Comment.Preproc */ +.highlight .cpf { color: #75715e } /* Comment.PreprocFile */ +.highlight .c1 { color: #75715e } /* Comment.Single */ +.highlight .cs { color: #75715e } /* Comment.Special */ +.highlight .gd { color: #f92672 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gi { color: #a6e22e } /* Generic.Inserted */ +.highlight .go { color: #66d9ef } /* Generic.Output */ +.highlight .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #75715e } /* Generic.Subheading */ +.highlight .kc { color: #66d9ef } /* Keyword.Constant */ +.highlight .kd { color: #66d9ef } /* Keyword.Declaration */ +.highlight .kn { color: #f92672 } /* Keyword.Namespace */ +.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ +.highlight .kr { color: #66d9ef } /* Keyword.Reserved */ +.highlight .kt { color: #66d9ef } /* Keyword.Type */ +.highlight .ld { color: #e6db74 } /* Literal.Date */ +.highlight .m { color: #ae81ff } /* Literal.Number */ +.highlight .s { color: #e6db74 } /* Literal.String */ +.highlight .na { color: #a6e22e } /* Name.Attribute */ +.highlight .nb { color: #f8f8f2 } /* Name.Builtin */ +.highlight .nc { color: #a6e22e } /* Name.Class */ +.highlight .no { color: #66d9ef } /* Name.Constant */ +.highlight .nd { color: #a6e22e } /* Name.Decorator */ +.highlight .ni { color: #f8f8f2 } /* Name.Entity */ +.highlight .ne { color: #a6e22e } /* Name.Exception */ +.highlight .nf { color: #a6e22e } /* Name.Function */ +.highlight .nl { color: #f8f8f2 } /* Name.Label */ +.highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +.highlight .nx { color: #a6e22e } /* Name.Other */ +.highlight .py { color: #f8f8f2 } /* Name.Property */ +.highlight .nt { color: #f92672 } /* Name.Tag */ +.highlight .nv { color: #f8f8f2 } /* Name.Variable */ +.highlight .ow { color: #f92672 } /* Operator.Word */ +.highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ +.highlight .mf { color: #ae81ff } /* Literal.Number.Float */ +.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ +.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ +.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ +.highlight .sa { color: #e6db74 } /* Literal.String.Affix */ +.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ +.highlight .sc { color: #e6db74 } /* Literal.String.Char */ +.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ +.highlight .sd { color: #e6db74 } /* Literal.String.Doc */ +.highlight .s2 { color: #e6db74 } /* Literal.String.Double */ +.highlight .se { color: #ae81ff } /* Literal.String.Escape */ +.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ +.highlight .si { color: #e6db74 } /* Literal.String.Interpol */ +.highlight .sx { color: #e6db74 } /* Literal.String.Other */ +.highlight .sr { color: #e6db74 } /* Literal.String.Regex */ +.highlight .s1 { color: #e6db74 } /* Literal.String.Single */ +.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ +.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #a6e22e } /* Name.Function.Magic */ +.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ +.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ +.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ +.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/en/1.0/explanation.html b/docs/en/1.0/explanation.html new file mode 100644 index 0000000..f7a4800 --- /dev/null +++ b/docs/en/1.0/explanation.html @@ -0,0 +1,227 @@ + + + + + + + + Explanation - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/explanation/async.html b/docs/en/1.0/explanation/async.html new file mode 100644 index 0000000..dbd597d --- /dev/null +++ b/docs/en/1.0/explanation/async.html @@ -0,0 +1,372 @@ + + + + + + + + Asynchronous Programming 101 - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Asynchronous Programming 101

    +
    +

    The Story

    +

    Let’s say we want to build a search engine. We’ll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this:

    +../_images/why_single_task.png +

    We have lots of web pages to index, so we simply handle them one by one:

    +../_images/why_throughput.png +

    Let’s assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores:

    +../_images/why_multicore.png +

    This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading:

    +../_images/why_multithreading.png +

    Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What’s wrong with multi-threading? From the diagram we can see:

    +
      +
    • There are yellow bars taking up extra time.

    • +
    • The green bars can still overlap with any bar in the other thread, but

    • +
    • non-green bars cannot overlap with non-green bars in the other thread.

    • +
    +

    The yellow bars are time taken by context switches, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let’s assume a world without +Hyper-threading or similar), +so in order to run several threads concurrently the CPU must split its +time into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point.

    +

    Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it’s waiting for the HTTP response (I/O). That’s how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won’t be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That’s also why this is called concurrency instead of parallelism.

    +

    As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous C10k +problem, usually solved by +asynchronous I/O:

    +../_images/why_coroutine.png +
    +

    Note

    +

    Asynchronous I/O and coroutines are two different things, but they usually +go together. Here we will stick with coroutines for simplicity.

    +
    +

    Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem.

    +
    +
    +

    Cooperative multitasking

    +

    So what is a coroutine?

    +

    In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread.

    +

    Threads are scheduled by the operating system using an approach called preemptive +multitasking. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn’t +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this:

    +
    Thread 1: I wanna run!
    +OS: Okay, here you go...
    +Thread 2: I wanna run!
    +OS: Urh, alright one sec ... Thread 1, hold on for a while!
    +Thread 1: Well I'm not done yet, but you are the boss.
    +OS: It won't be long. Thread 2 it's your turn now.
    +Thread 2: Yay! (&%#$@..+*&#)
    +Thread 1: Can I run now?
    +OS: Just a moment please ... Thread 2, give it a break!
    +Thread 2: Alright ... but I really need the CPU.
    +OS: You'll have it later. Thread 1, hurry up!
    +
    +
    +

    In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don’t - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +cooperative multitasking. It’s like this:

    +
    Coroutine 1: Let me know when event A arrives. I'm done here before that.
    +Event manager: Okay. What about you, coroutine 2?
    +Coroutine 2: Um I've got nothing to do here before event B.
    +Event manager: Cool, I'll be watching.
    +Event manager: (after a while) Hey coroutine 1, event A is here!
    +Coroutine 1: Awesome! Let me see ... looks good, but I need event C now.
    +Event manager: Very well. Seems event B arrived just now, coroutine 2?
    +Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done.
    +Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while.
    +(silence)
    +Event manager: Damn, event C timed out!
    +Coroutine 1: Arrrrh gotta kill myself with an exception :S
    +Event manager: Up to you :/
    +
    +
    +

    For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That’s why in the last diagram, +the red bars are not interleaved like threads.

    +
    +

    Tip

    +

    In Python and asyncio, async def declares coroutines, await yields +control to event loop (event manager).

    +
    +
    +
    +

    Pros and cons

    +

    Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput.

    +

    With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code.

    +

    However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple recv() operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to recv(), repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O.

    +

    Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, sleep(1) +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between await finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind.

    +

    Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It’s not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/explanation/engine.html b/docs/en/1.0/explanation/engine.html new file mode 100644 index 0000000..e1449bc --- /dev/null +++ b/docs/en/1.0/explanation/engine.html @@ -0,0 +1,679 @@ + + + + + + + + Engine and Connection - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Engine and Connection

    +

    GinoEngine is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together:

    +../_images/engine.png +

    Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users.

    +

    During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use.

    +
    +

    Note

    +

    In SQLAlchemy, database drivers are supposed to follow the DB-API standard, +which does not usually provide a pool implementation. Therefore, SQLAlchemy +has its own pool implementation, created directly in engine. This is where +this diagram doesn’t fit SQLAlchemy.

    +
    +

    The pool creates raw connections, not the GinoConnection +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we’ll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries.

    +

    On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use.

    +
    +

    Note

    +

    Another difference to SQLAlchemy here: GINO execution methods always return +final results, while in SQLAlchemy accessing the result may cause further +implicit database accesses. Therefore GINO engine immediately releases the +connection when the execution method on the engine returns, but SQLAlchemy +can only release the connection implicitly when the result data is found +exhausted.

    +

    By immediately releasing a connection, GINO may not release the related raw +connection when the raw connection was reused from another parent +connection. We’ll get to this later.

    +
    +

    GINO also supports implicit execution +without having to specify an engine or connection explicitly. This is done by +binding the engine to the db instance, also known as the +MetaData or the Gino instance. +You may possibly bind a GinoConnection instance, but that +is greatly not recommended because it is very much untested.

    +

    At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +db instance on creation, therefore the models can do implicit execution too +if their db has a bind.

    +

    Then let’s get to some details.

    +
    +

    Creating Engines

    +

    GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO’s strategy to create asynchronous GinoEngine is +just gino, but only available after gino is imported:

    +
    import gino, sqlalchemy
    +
    +async def main():
    +    e = await sqlalchemy.create_engine('postgresql://...', strategy='gino')
    +    # e is a GinoEngine
    +
    +
    +
    +

    Tip

    +

    Please read this SQLAlchemy document +to learn about writing database URLs.

    +
    +

    Also the GINO strategy replaces the default driver of dialect postgresql:// +from psycopg2 to asyncpg, so that you don’t have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +postgresql+asyncpg://... or just asyncpg://....

    +

    GINO also offers a shortcut as gino.create_engine(), which only sets the +default strategy to gino and does nothing more. So here is an identical +example:

    +
    import gino
    +
    +async def main():
    +    e = await gino.create_engine('postgresql://...')
    +    # e is also a GinoEngine
    +
    +
    +

    As you may have noticed, when using the GINO strategy, +create_engine() returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default.

    +

    For it is just SQLAlchemy create_engine(), the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!):

    +

    For Dialect:

    + +

    For Engine:

    + +

    While these parameters are discarded by GINO:

    + +

    In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from create_pool(). +For example, we can create an engine without initial connections:

    +
    e = await gino.create_engine('postgresql://...', min_size=0)
    +
    +
    +

    Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:

    +
    import sqlalchemy
    +
    +metadata = sqlalchemy.MetaData()
    +metadata.bind = 'postgresql://...'
    +
    +# or in short
    +
    +metadata = sqlalchemy.MetaData('postgresql://...')
    +
    +
    +

    This implicitly calls create_engine() under the hood. However +in GINO, creating an engine requires await, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass Gino, reverted it to simple assignment:

    +
    import gino
    +
    +db = gino.Gino()
    +
    +async def main():
    +    # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind
    +    engine = await gino.create_engine('postgresql://...')
    +    db.bind = engine
    +
    +
    +

    And provided a shortcut to do so:

    +
    engine = await db.set_bind('postgresql://...')
    +
    +
    +

    And another simpler shortcut for one-time usage:

    +
    db = await gino.Gino('postgresql://...')
    +
    +
    +

    To unset a bind and close the engine:

    +
    engine, db.bind = db.bind, None
    +await engine.close()
    +
    +
    +

    Or with a shortcut correspondingly:

    +
    await engine.pop_bind().close()
    +
    +
    +

    Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +
    +
    +

    Managing Connections

    +

    With a GinoEngine at hand, you can acquire connections +from the pool now:

    +
    conn = await engine.acquire()
    +
    +
    +

    Don’t forget to release it after use:

    +
    await conn.release()
    +
    +
    +

    Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    Here conn is a GinoConnection instance. As mentioned +previously, GinoConnection is mapped to an underlying raw +connection, as shown in following diagram:

    +../_images/connection.png +

    Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: reuse and +lazy. They are keyword arguments on acquire() +and by default switched off.

    +
    +

    reuse

    +

    When acquiring a GinoConnection (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +GinoConnection (2). This is the default behavior of +acquire() with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:

    +
    async with engine.acquire() as conn1:
    +    async with engine.acquire() as conn2:
    +        # conn2 is a completely different connection than conn1
    +
    +
    +

    But sometimes conn2 may exist in a different method:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner()
    +
    +async def inner():
    +    async with engine.acquire() as conn2:
    +        # ...
    +
    +
    +

    And we probably wish inner could reuse the same raw connection in +outer to save some resource, or borrow a new one if inner is +individually called without outer:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner(conn2=None):
    +    if conn2 is None:
    +        async with engine.acquire() as conn2:
    +            # ...
    +    else:
    +        # the same ... again
    +
    +
    +

    This is exactly the scenario reuse could be useful. We can simply tell the +acquire() to reuse the most recent reusable +connection in current context by setting reuse=True, as presented in this +identical example:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner():
    +    async with engine.acquire(reuse=True) as conn2:
    +        # ...
    +
    +
    +

    Back to previous diagram, the blue GinoConnection +instances (3, 4, 6) are “reusing connections” acquired with reuse=True, +while the green ones (2, 5, 7) are not, thus they become “reusable +connections”. The green reusable connections are put in a stack in current +context, so that acquire(reuse=True) always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack.

    +
    +

    Tip

    +

    By context, we are actually referring to the context concept in +contextvars the +new module in Python 3.7, and its partial backport aiocontextvars. Simply speaking, you may +treat a series of function calls in a chain as in the same context, even if +there is an await. It’s something like a thread local in asyncio.

    +
    +

    GinoConnection (2) may be created through +acquire(reuse=True) too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection.

    +

    Releasing a reusing connection won’t cause the reused raw connection being +returned to the pool, only directly releasing the reused +GinoConnection can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more.

    +
    +
    +

    lazy

    +

    As you may have found, GinoConnection (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set lazy=True on acquire.

    +

    A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, GinoConnection (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +
    +
    +

    On implementation level, lazy is extremely easy in +acquire(): if lazy=False then borrow a raw +connection, else do nothing. That’s it. Before executing a query or starting a +transaction, GinoConnection will always try to borrow a +raw connection if there is none present. This allows GINO to “transiently +release” a raw connection, while all GinoConnection +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don’t want to +waste a connection resource checked out for nothing. For example:

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +    await conn.release(permanent=False)        # release (8)
    +    await asyncio.sleep(10)                    # simulate long I/O work
    +    await conn.scalar('select now()')          # re-acquire a new raw connection,
    +                                               #   not necessarily the same (8)
    +
    +
    +

    When used together with reuse, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set lazy=False on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It’s been quite a while, let me post the same diagram again:

    +../_images/connection.png +
    +
    +

    reusable

    +

    Usually, you don’t have to worry about the two options reuse and lazy, +using the default acquire() will always create +a concrete GinoConnection with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use reusable=False on acquire. As shown in the diagram, the unreusable +GinoConnection is an orphan away from any stack:

    +
    async with engine.acquire():                    # (2)
    +    async with engine.acquire(reusable=False):  # the unreusable connection
    +        async with engine.acquire(reuse=True):  # (3)
    +
    +
    +

    Unreusable connections can be lazy. But it is usually meaningless to specify +both reuse=True and reusable=False at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn’t make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don’t use +acquire(reuse=True, reusable=False) unless you know what it does.

    +
    +
    +

    current_connection

    +

    Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +current_connection which is always the reusable +GinoConnection at the top of current stack, or None +if current stack is empty.

    +
    +

    Tip

    +

    The different between current_connection +and acquire(reuse=True) is, the +latter always produces a GinoConnection, while the +former may not.

    +
    +
    +
    +
    +

    Executing Queries

    +

    Once you have a GinoConnection instance, you can start +executing queries with it. There are 6 variants of the execute method: +all(), +first(), +one(), +one_or_none(), +scalar() and +status(). They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results:

    +
      +
    • all() returns all results in a +list, which may be empty when the query has no result, empty but +still a list.

    • +
    • first() returns the first result directly, +or None if there is no result at all. There is usually some optimization +behind the scene to efficiently get only the first result, instead of loading +the full result set into memory.

    • +
    • one() returns exactly one result. If there +is no result at all or if there are multiple results, an exception is raised.

    • +
    • one_or_none() is similar to +one(), but it returns None if there is +no result instead or raising an exception.

    • +
    • scalar() is similar to +first(), it returns the first value of the +first result. Quite convenient to just retrieve a scalar value from database, +like NOW(), MAX(), COUNT() or whatever generates a single value. +None is also returned when there is no result, it is up to you how to +distinguish no result and the first value is NULL.

    • +
    • status() executes the query and discard all +the query results at all. Instead it returns the execution status line as it +is, usually a textual string. Note, there may be no optimization to only +return the status without loading the results, so make your query generate +nothing if you don’t want any result.

    • +
    +

    By “result”, I meant RowProxy of SQLAlchemy - an +immutable row instance with both tuple and dict interfaces. +Database values are translated twice before they are eventually stored in a +RowProxy: first by the database driver (dialect) +from network payload to Python objects (see Type Conversion of +how asyncpg does this), second by SQLAlchemy +result_processor() depending on the actual +type and dialect.

    +

    The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy execute() (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +multiparams, all 4 methods will always return None discarding all +results. Likewise, the parameter values are processed twice too: first by +bind_processor() then the database driver.

    +

    GINO also supports SQLAlchemy +execution_options() provided either on +engine level, +connection level or on +queries. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling return_model and providing a model will make +all() and +first() return ORM model instance(s) instead +of RowProxy instance(s). See also +execution_options() for more information.

    +

    In addition, GINO has an iterate() method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a server-side cursor.

    +
    +
    +

    Implicit Execution

    +

    Acquire a GinoConnection and execute queries on it, that +is the most explicit way. You can also execute queries on a +GinoEngine instance. In this case, a connection will be +acquired with reuse=True for you implicitly, and released after returning:

    +
    await engine.scalar('select now()')
    +
    +
    +

    Equals to:

    +
    async with engine.acquire(reuse=True) as conn:
    +    await conn.scalar('select now()')
    +
    +
    +

    This allows you to easily write connectionless code. For example:

    +
    async def get_now():
    +    return await engine.scalar('select now()')
    +
    +async def main():
    +    async with engine.acquire():
    +        now = await get_now()
    +        await engine.status('UPDATE ...')
    +
    +
    +

    In this example, main() will take only one raw connection. get_now() +can also work alone out of any acquire() context, thanks to reuse.

    +

    Furthermore, GINO provides the same query APIs on Gino +directly. They are simply delegates to corresponding API methods on the +bind. This allows even engine-less programming:

    +
    db = gino.Gino()
    +
    +async def get_now():
    +    return await db.scalar('select now()')
    +
    +async def main():
    +    async with db.with_bind('postgresql://...'):
    +        now = await get_now()
    +        await db.status('UPDATE ...')
    +
    +
    +
    +

    Note

    +

    In this example we didn’t put the two queries in an acquire() block, so +they might be executed in two different connections.

    +
    +

    At last, the SQLAlchemy implicit execution +on queries also work in GINO, under an extension named gino:

    +
    await users_table.select().gino.all()
    +
    +
    +

    By default, the extension GinoExecutor is injected on +Executable as a property of name gino +at the creation of Gino instance. Therefore, any +Executable object has the gino +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the bind of the db instance.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/explanation/why.html b/docs/en/1.0/explanation/why.html new file mode 100644 index 0000000..532f6ff --- /dev/null +++ b/docs/en/1.0/explanation/why.html @@ -0,0 +1,407 @@ + + + + + + + + Why Asynchronous ORM? - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Why Asynchronous ORM?

    +

    Conclusion first: in many cases, you don’t need to use an asynchronous ORM, or even +asyncio itself. But when you do, it’s very important to get it done correctly.

    +
    +

    When asyncio Helps

    +

    As Mike Bayer - the author of SQLAlchemy - pointed out, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn’t make sense to say “Hey I wanna use asyncio +because I love this asynchronous ORM”.

    +

    The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +Asynchronous Programming 101. So the ultimate reason to use asyncio should be the application itself, +not the database.

    +

    For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn’t send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it’ll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead.

    +../_images/263px-Minimum-Tonne.svg.png +

    Another example is authentication using OpenID Connect Authorization Code Flow as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it’s delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +Liebig’s law, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it’s not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave.

    +

    Arbitrary delays may happen in the database too. In PostgreSQL, you can LISTEN to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case.

    +

    In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is:

    +
    +
    +

    How to Access Database

    +

    The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let’s assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don’t get me wrong - asyncpg is great and convenient to use, but +there won’t be such a question “why asynchronous ORM” if we’re not seeking an objective +layer over bare SQL. It’s totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it’s just out of our scope here.

    +

    Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won’t work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here’s a very simple server:

    +
    import asyncio
    +from fastapi import FastAPI
    +from sqlalchemy import create_engine
    +from sqlalchemy.orm import sessionmaker
    +
    +app = FastAPI()
    +e = create_engine("postgresql://localhost")
    +Session = sessionmaker(bind=e)
    +
    +
    +@app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    This follows advices found in When do I construct a Session, when do I commit it, and when do I close it? from SQLAlchemy - linking +the session scope to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is kill -9.

    +

    What just happened is called a resource starvation. While the first 10 +requests were waiting for the async work (sleep(0.1)), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default max_overflow=10) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th’s acquisition.

    +

    The root cause of such starvation is making asynchronous calls in a transaction +scope created implicitly by the default autocommit=False. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in transaction scopes by explicitly ending them:

    +
    @app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        session.rollback()  # return the connection to avoid starvation
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    Because of the implicit nature of typical ORMs, it’s very hard to identify all such +transaction scopes and make sure there’s no await in them - you may have started +an implicit transaction by simply accessing a property like current_user.name. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this.

    +

    What about thread pool?

    +

    The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let’s assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool.

    +

    This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you’d want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you’re doing that in +the opposite way, you may want to reconsider the design.

    +

    In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs.

    +
    +
    +

    Asynchronous ORM Done Right

    +

    I would describe a proper asynchronous ORM as follows:

    +
    +

    Don’t Starve.

    +

    The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything async.

    +

    In the example above, if the connection acquisition is asynchronous, it won’t block the +main thread any more (it’ll block it’s own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:

    +
    @app.get("/")
    +async def read_root():
    +    async with db.acquire() as conn:  # }
    +        async with conn.begin():      # } These two won't block the main thread
    +            now = await db.scalar("SELECT now()")
    +            await asyncio.sleep(0.1)  # do some async work
    +            return str(now)
    +
    +
    +

    Even though this code won’t cause any resource starvation, using await within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won’t kill the server immediately, but +it has a few disadvantages:

    +
      +
    1. As the database connection pool is usually much smaller than the asynchronous server +concurrency, this kind of code caps the concurrency down to the DB pool level.

    2. +
    3. Taking a database connection from the pool for nothing is a waste of resource, +especially when the database pool is the shortest stave.

    4. +
    5. Database transactions should be kept short as much as possible. Because long-hanging +transactions may keep certain database locks, leading to performance issues or even +triggering a chain reaction of deadlocks.

    6. +
    +
    +
    +

    Be explicit.

    +

    With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an await or async with, so that you’ll know for sure when a +statement is trying to make any database I/O. Fortunately there’s no other way in +asyncio to do this - following the pattern of Twisted, asyncio is already forcing +explicit await for any asynchronous operations.

    +

    Being explicit in other part of the ORM design is also useful for enhancing the quality +of users’ code. This has nothing to do with asynchronous, it’s not a golden standard or +something with a clear cut either. For example:

    +
      +
    • We could design the ORM model instances to be stateless, so that the users don’t have +to learn and worry about maintaining the state of the instances.

    • +
    • There shouldn’t be any “buffered operations” which users could “flush” with a single +statement once for all.

    • +
    • The user should just give direct one-off commands and the ORM executes them right away.

    • +
    • Also I think the convenience tooling should be well-balanced, the user doesn’t have to +guess or remember what an API means - for example, there’re more than one ways to load +a many-to-one relationship, I’d prefer to write the query by myself rather than trying +to remember what “join_without_n_plus_1()” means.

    • +
    +
    +
    +

    Be productive.

    +

    Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two?

    +

    I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn’t harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO’s design.

    +

    Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they’ve learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again.

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/genindex.html b/docs/en/1.0/genindex.html new file mode 100644 index 0000000..270c3b2 --- /dev/null +++ b/docs/en/1.0/genindex.html @@ -0,0 +1 @@ +

    genindex.html

    \ No newline at end of file diff --git a/docs/en/1.0/how-to.html b/docs/en/1.0/how-to.html new file mode 100644 index 0000000..c05fc43 --- /dev/null +++ b/docs/en/1.0/how-to.html @@ -0,0 +1,274 @@ + + + + + + + + How-to Guides - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + + + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/alembic.html b/docs/en/1.0/how-to/alembic.html new file mode 100644 index 0000000..e591c6a --- /dev/null +++ b/docs/en/1.0/how-to/alembic.html @@ -0,0 +1,294 @@ + + + + + + + + Use Alembic - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Use Alembic

    +

    Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO.

    +

    To add migrations to project first of all, add alembic as dependency:

    +
    $ pip install --user alembic
    +
    +
    +

    When you need to set up alembic for your project.

    +

    Prepare sample project. We will have a structure:

    +
    alembic_sample/
    +    my_app/
    +        models.py
    +
    +
    +

    Inside models.py define simple DB Model with GINO:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +
    +

    Set up Alembic

    +

    This will need to be done only once. Go to the main folder of your project +alembic_sample and run:

    +
    $ alembic init alembic
    +
    +
    +

    Alembic will create a bunch of files and folders in your project directory. One of them +will be alembic.ini. Open alembic.ini (you can find it in the main project +folder alembic_sample). Now change property sqlalchemy.url = with your DB +credentials. Like this:

    +
    sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    +
    +
    +

    Next go to folder alembic/ and open env.py file. Inside the env.py file you +need to import the db object. In our case db object is db from models +modules. This is a variable that links to your Gino() instance.

    +

    Inside alembic/env.py:

    +
    from main_app.models import db
    +
    +
    +

    And change target_metadata = to:

    +
    target_metadata = db
    +
    +
    +

    That’s it. We finished setting up Alembic for a project.

    +
    +

    Note

    +

    All alembic commands must be run always from the folder that contains the +alembic.ini file.

    +
    +
    +
    +

    Create first migration revision

    +

    Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema.

    +
    $ alembic revision -m "first migration" --autogenerate --head head
    +
    +
    +

    If you have any problems relative to package imports similar to this example:

    +
    File "alembic/env.py", line 7, in <module>
    +    from main_app.models import db
    +ModuleNotFoundError: No module named 'main_app'
    +
    +
    +

    Either install your project locally with pip install -e ., poetry install or +python setup.py develop, or add you package to PYTHONPATH, like this:

    +
    $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample
    +
    +
    +

    After the successful run of alembic revision in folder alembic/versions you will +see a file with new migration.

    +
    +
    +

    Apply migration on DB

    +

    Now time to apply migration to DB. It will create tables based on you DB Models.

    +
    $ alembic upgrade head
    +
    +
    +

    Great. Now you apply your first migration. Congratulations!

    +

    Next time, when you will make any changes in DB models just do:

    +
    $ alembic revision -m "your migration description" --autogenerate --head head
    +
    +
    +

    And

    +
    alembic upgrade head
    +
    +
    +

    Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/contributing.html b/docs/en/1.0/how-to/contributing.html new file mode 100644 index 0000000..113630b --- /dev/null +++ b/docs/en/1.0/how-to/contributing.html @@ -0,0 +1,357 @@ + + + + + + + + Contributing - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Contributing

    +

    Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given.

    +

    You can contribute in many ways:

    +
    +

    Types of Contributions

    +
    +

    Report Bugs

    +

    Report bugs at https://github.com/python-gino/gino/issues.

    +

    If you are reporting a bug, please include:

    +
      +
    • Your operating system name and version.

    • +
    • Any details about your local setup that might be helpful in troubleshooting.

    • +
    • Detailed steps to reproduce the bug.

    • +
    +
    +
    +

    Fix Bugs

    +

    Look through the GitHub issues for bugs. Anything tagged with “bug” +and “help wanted” is open to whoever wants to implement it.

    +
    +
    +

    Implement Features

    +

    Look through the GitHub issues for features. Anything tagged with “enhancement” +and “help wanted” is open to whoever wants to implement it.

    +
    +
    +

    Write Documentation

    +

    GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such.

    +
    +
    +

    Submit Feedback

    +

    The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues.

    +

    If you are proposing a feature:

    +
      +
    • Explain in detail how it would work.

    • +
    • Keep the scope as narrow as possible, to make it easier to implement.

    • +
    • Remember that this is a volunteer-driven project, and that contributions +are welcome :)

    • +
    +
    +
    +
    +

    Get Started!

    +

    Ready to contribute? Here’s how to set up gino for local development.

    +
      +
    1. Fork the gino repo on GitHub.

    2. +
    3. Clone your fork locally:

      +
      $ git clone git@github.com:your_name_here/gino.git
      +
      +
      +
    4. +
    5. Create a branch for local development:

      +
      $ cd gino/
      +$ git checkout -b name-of-your-bugfix-or-feature
      +
      +
      +
    6. +
    +

    Now you can make your changes locally.

    +
      +
    1. Create virtual environment. Example for virtualenvwrapper:

      +
      $ mkvirtualenv gino
      +
      +
      +
    2. +
    3. Activate the environment and install requirements:

      +
      $ pip install -r requirements_dev.txt
      +
      +
      +
    4. +
    5. When you’re done making changes, check that your changes pass syntax checks:

      +
      $ flake8 gino tests
      +
      +
      +
    6. +
    +

    7. And tests (including other Python versions with tox). +For tests you you will need running database server (see “Tips” section below for configuration details):

    +
    $ pytest tests
    +$ tox
    +
    +
    +
      +
    1. For docs run:

      +
      $ make docs
      +
      +
      +
    2. +
    +

    It will build and open up docs in your browser.

    +
      +
    1. Commit your changes and push your branch to GitHub:

      +
      $ git add .
      +$ git commit -m "Your detailed description of your changes."
      +$ git push origin name-of-your-bugfix-or-feature
      +
      +
      +
    2. +
    3. Submit a pull request through the GitHub website.

    4. +
    +
    +
    +

    Pull Request Guidelines

    +

    Before you submit a pull request, check that it meets these guidelines:

    +
      +
    1. The pull request should include tests.

    2. +
    3. If the pull request adds functionality, the docs should be updated. Put +your new functionality into a function with a docstring, and add the +feature to the list in README.rst.

    4. +
    5. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check +https://github.com/python-gino/gino/actions?query=event%3Apull_request +and make sure that the tests pass for all supported Python versions.

    6. +
    +
    +
    +

    Tips

    +

    To run a subset of tests:

    +
    $ py.test -svx tests.test_gino
    +
    +
    +

    By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using ‘psql’ or similar:

    +
    CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino';
    +CREATE DATABASE gino WITH OWNER = gino;
    +
    +
    +

    Then run the tests like so:

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino
    +$ py.test
    +
    +
    +

    Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):

    +
    $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
    +$ openssl rsa -in privkey.pem -passin pass:abcd -out server.key
    +$ openssl req -x509 -in server.req -text -key server.key -out server.crt
    +$ chmod 600 server.key
    +$ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    +
    +
    +

    Terminal 2 (client):

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433
    +$ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'"
    +$ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;"
    +$ pytest tests/test_aiohttp.py
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/crud.html b/docs/en/1.0/how-to/crud.html new file mode 100644 index 0000000..4be0f36 --- /dev/null +++ b/docs/en/1.0/how-to/crud.html @@ -0,0 +1,192 @@ + + + + + + + + CRUD - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    CRUD

    +

    THIS IS A WIP

    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/faq.html b/docs/en/1.0/how-to/faq.html new file mode 100644 index 0000000..e629dcf --- /dev/null +++ b/docs/en/1.0/how-to/faq.html @@ -0,0 +1,555 @@ + + + + + + + + Frequently Asked Questions - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Frequently Asked Questions

    +
    +

    SQLAlchemy 1.4 supports asyncio, what will GINO be?

    +

    Starting from 1.4, SQLAlchemy will support asyncio. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +greenlet. As an “async” user, you don’t +need to worry about its internals in most cases, it’s fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed.

    +

    Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features:

    +
      +
    • Contextual Connections (see Engine and Connection)

    • +
    • SQLAlchemy Core-based CRUD models

    • +
    • The GINO Loader system

    • +
    • Async MySQL support

    • +
    • Typing support NEW

    • +
    • Trio support NEW

    • +
    • Execution performance NEW

    • +
    +

    As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized.

    +
    +
    +

    ORM or not ORM?

    +

    GINO does perform the Object-Relational Mapping work under the +Data Mapper Pattern, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them “dirty” - or in a different way of thinking +they are always “dirty”. Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely non-ORM way.

    +
    +
    +

    Can I use features of SQLAlchemy ORM?

    +

    SQLAlchemy has two parts:

    +
      +
    • SQLAlchemy Core

    • +
    • SQLAlchemy ORM

    • +
    +

    GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won’t work in GINO.

    +
    +
    +

    How to join?

    +

    GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know how to write join in +SQLAlchemy. +Especially, Ádám made some amazing upgrades in +GINO #113 to make join easier, so +that you can use model classes directly as if they are tables in joining:

    +
    results = await User.join(Book).select().gino.all()
    +
    +
    +
    +
    +

    How to connect to database through SSL?

    +

    It depends on the dialect and database driver. For asyncpg, keyword arguments +on asyncpg.connect() are directly +available on create_engine() or db.set_bind(). Therefore, enabling SSL is rather easy:

    +
    engine = await gino.create_engine(..., ssl=True)
    +
    +
    +
    +
    +

    What is aiocontextvars and what does it do?

    +

    It is a partial backport of the new built-in module contextvars introduced in Python +3.7. In Python 3.5 and 3.6, aiocontextvars patches loop.create_task() +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2

    +

    If you are using Python 3.7, then aiocontextvars does nothing at all.

    +
    +

    Note

    +

    This answer is for GINO 0.8 and later, please check earlier versions of +this documentation if you are using GINO 0.7.

    +
    +
    +
    +

    How to define relationships?

    +

    GINO 1.0 or lower doesn’t provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see Loaders and Relationship for more information.

    +
    +
    +

    How to define index with multiple columns?

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    first_name = db.Column(db.Unicode())
    +    last_name = db.Column(db.Unicode())
    +
    +    _name_idx = db.Index('index_on_name', 'first_name', 'last_name')
    +
    +
    +

    The _name_idx is not used.

    +
    +
    +

    Is there a django admin interface for GINO?

    +

    Not quite yet, please follow this discussion.

    +
    +
    +

    How to use multiple databases for different users on the fly?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query.

    +

    In order to use multiple databases, you would need multiple +GinoEngine instances. Here’s a full example using FastAPI with +lazy engine creation:

    +
    from asyncio import Future
    +from contextvars import ContextVar
    +
    +from fastapi import FastAPI, Request
    +from gino import create_engine
    +from gino.ext.starlette import Gino
    +
    +engines = {}
    +dbname = ContextVar("dbname")
    +
    +
    +class ContextualGino(Gino):
    +    @property
    +    def bind(self):
    +        e = engines.get(dbname.get(""))
    +        if e and e.done():
    +            return e.result()
    +        else:
    +            return self._bind
    +
    +    @bind.setter
    +    def bind(self, val):
    +        self._bind = val
    +
    +
    +app = FastAPI()
    +db = ContextualGino(app)
    +
    +
    +@app.middleware("http")
    +async def lazy_engines(request: Request, call_next):
    +    name = request.query_params.get("db", "postgres")
    +    fut = engines.get(name)
    +    if fut is None:
    +        fut = engines[name] = Future()
    +        try:
    +            engine = await create_engine("postgresql://localhost/" + name)
    +        except Exception as e:
    +            fut.set_exception(e)
    +            del engines[name]
    +            raise
    +        else:
    +            fut.set_result(engine)
    +    await fut
    +    dbname.set(name)
    +    return await call_next(request)
    +
    +
    +@app.get("/")
    +async def get():
    +    return dict(dbname=await db.scalar("SELECT current_database()"))
    +
    +
    +
    +
    +

    How to load complex query results?

    +

    The API doc of gino.loader explains the available loaders, and there’re a few +examples in Loaders and Relationship too.

    +

    Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a TupleLoader with two sub-loaders - +ModelLoader and ColumnLoader:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Be ware of the tuple in .gino.load((...)).

    +
    +
    +

    How to do bulk or batch insert / update?

    +

    For a simple example, take a model that has one field, “name.” In your application you +have a list of names you would like to add to the database:

    +
    new_names = ["Austin", "Ali", "Jeff", "Marissa"]
    +
    +
    +

    To quickly insert the names in one query, first construct a dict with the +{"model_key": "value"} format:

    +
    new_names_dict = [dict(name=new_name) for new_name in new_names]
    +>> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}]
    +
    +
    +

    Finally, run an insert statement on the model:

    +
    await User.insert().gino.all(new_names_dict)
    +
    +
    +
    +
    +

    How to print the executed SQL?

    +

    GINO uses the same approach from SQLAlchemy: create_engine(..., echo=True). +(Or db.set_bind(..., echo=True)) Please see also here.

    +

    If you use any extension, you can also set that in config, by db_echo or DB_ECHO.

    +
    +
    +

    How to run EXISTS SQL?

    +
    await db.scalar(db.exists().where(User.email == email).select())
    +
    +
    +
    +
    +

    How to work with Alembic?

    +

    The fact that Gino is a MetaData is the +key to use Alembic. Just import and set target_metadata = db in Alembic env.py +will do. See Use Alembic for more details.

    +
    +
    +

    How to join the same table twice?

    +

    This is the same pattern as described in SQLAlchemy Adjacency List Relationships, where you +have a table with “a foreign key reference to itself”, or join the same table more than +once, “to represent hierarchical data in flat tables.” We’d need to use +alias(), for example:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.ForeignKey("users.id"))
    +
    +Parent = User.alias()
    +query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
    +users = await query.gino.load(User.load(parent=Parent)).all()
    +
    +
    +
    +
    +

    How to execute raw SQL with parameters?

    +

    Wrap the SQL with text(), and use keyword arguments:

    +
    query = db.text('SELECT * FROM users WHERE id = :id_val')
    +row = await db.first(query, id_val=1)
    +
    +
    +

    You may even load the rows into model instances:

    +
    query = query.execution_options(loader=User)
    +user = await db.first(query, id_val=1)
    +
    +
    +
    +
    +

    Gino engine is not initialized?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query. If bind is not set before +this, you’ll see this error:

    +
    gino.exceptions.UninitializedError: Gino engine is not initialized.
    +
    +
    +

    You could use either:

    +
      +
    • Call set_bind() or with_bind() to set the +bind on the Gino instance.

    • +
    • Use one of the Web framework extensions to set the bind for you in usually the server +start-up hook.

    • +
    • Use explicit bind for each execution, for example:

      +
      engine = await create_engine("...")
      +# ...
      +user = await User.get(request.user_id, bind=engine)
      +
      +
      +
    • +
    +
    +
    +

    How can I do SQL xxxx in GINO?

    +

    GINO uses SQLAlchemy Core queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy Table instances, and the column +attributes on GINO model classes are just SQLAlchemy Column +instances, you can use them in building your SQLAlchemy Core queries.

    +

    Alternatively, you could always execute the raw SQL directly, see How to execute raw SQL with parameters? above.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/json-props.html b/docs/en/1.0/how-to/json-props.html new file mode 100644 index 0000000..9a7090f --- /dev/null +++ b/docs/en/1.0/how-to/json-props.html @@ -0,0 +1,373 @@ + + + + + + + + JSON Property - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    JSON Property

    +

    GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields.

    +
    +

    Quick Start

    +
    from gino import Gino
    +from sqlalchemy.dialects.postgresql import JSONB
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +    birthday = db.DateTimeProperty()
    +
    +
    +

    The age and birthday are JSON properties stored in the profile column. You +may use them the same way as a normal GINO model field:

    +
    u = await User.create(name="daisy", age=18)
    +print(u.name, u.age)  # daisy 18
    +
    +
    +
    +

    Note

    +

    profile is the default column name for all JSON properties in a model. If you +need a different column name for some JSON properties, you’ll need to specify +explicitly:

    +
    audit_profile = db.Column(JSON, nullable=False, server_default="{}")
    +
    +access_log = db.ArrayProperty(prop_name="audit_profile")
    +abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
    +
    +
    +
    +

    Using JSON properties in queries is supported:

    +
    await User.query.where(User.age > 16).gino.all()
    +
    +
    +

    This is simply translated into a native JSON query like this:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
    +
    +
    +

    Datetime type is very much the same:

    +
    from datetime import datetime
    +
    +await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
    +
    +
    +

    And the generated SQL:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
    +-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
    +
    +
    +

    Here’s a list of all the supported JSON properties:

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    JSON Property

    Python type

    JSON type

    Database Type

    StringProperty

    str

    string

    text

    IntegerProperty

    int

    number

    int

    BooleanProperty

    bool

    boolean

    boolean

    DateTimeProperty

    datetime

    string

    text

    ObjectProperty

    dict

    object

    JSON

    ArrayProperty

    list

    array

    JSON

    +
    +
    +

    Hooks

    +

    JSON property provides 2 instance-level hooks to customize the data:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @age.before_set
    +    def age(self, val):
    +        return val - 1
    +
    +    @age.after_get
    +    def age(self, val):
    +        return val + 1
    +
    +u = await User.create(name="daisy", age=18)
    +print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
    +
    +
    +

    And 1 class-level hook to customize the SQLAlchemy expression of the property:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    height = db.JSONProperty()
    +
    +    @height.expression
    +    def height(cls, exp):
    +        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
    +
    +
    +
    +
    +

    Create Index on JSON Properties

    +

    We’ll need to use declared_attr() to wait until the model class +is initialized. The rest is very much the same as defining a usual index:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @db.declared_attr
    +    def age_idx(cls):
    +        return db.Index("age_idx", cls.age)
    +
    +
    +

    This will lead to the SQL below executed if you run db.gino.create_all():

    +
    CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
    +
    +
    +
    +

    Warning

    +

    Alembic doesn’t support auto-generating revisions for functional indexes yet. You’ll +need to manually edit the revision. Please follow this issue for updates.

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/loaders.html b/docs/en/1.0/how-to/loaders.html new file mode 100644 index 0000000..a76b93f --- /dev/null +++ b/docs/en/1.0/how-to/loaders.html @@ -0,0 +1,636 @@ + + + + + + + + Loaders and Relationship - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Loaders and Relationship

    +

    Loaders are used to load database row results into objects.

    +

    GINO doesn’t support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined.

    +
    +

    Model Loader

    +

    The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:

    +
    query = db.select([User])
    +rows = await query.gino.all()
    +
    +
    +

    In order to load rows into User objects, you can provide an execution +option loader with a new ModelLoader instance:

    +
    from gino.loader import ModelLoader
    +
    +query = db.select([User])
    +query = query.execution_options(loader=ModelLoader(User))
    +users = await query.gino.all()
    +
    +
    +

    The ModelLoader would then load each database row into a +User object. As this is frequently used, GINO made it a shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User.load())
    +users = await query.gino.all()
    +
    +
    +

    And another shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User)
    +users = await query.gino.all()
    +
    +
    +
    +

    Tip

    +

    User as loader is transformed into ModelLoader(User) by +Loader.get(), explained later in “Loader +Expression”.

    +
    +

    And again:

    +
    query = db.select([User])
    +users = await query.gino.load(User).all()
    +
    +
    +

    This is identical to the normal CRUD query:

    +
    users = await User.query.gino.all()
    +
    +
    +
    +
    +

    Loader Expression

    +

    So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than ModelLoader, +there’re also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders.

    +

    Here is an example using all loaders at once:

    +
    uid, user, sep, cols = await db.select([User]).gino.load(
    +    (
    +        User.id,
    +        User,
    +        '|',
    +        lambda row, ctx: len(row),
    +    )
    +).first()
    +
    +
    +

    Let’s check this piece by piece. Overall, the argument of +load() is a tuple. This is interpreted into a +TupleLoader, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a TupleLoader is a tuple.

    +

    Column in Loader Expressions are interpreted as +ColumnLoader. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, ColumnLoader uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use ColumnLoader, +you’ll have to declare columns for the query:

    +
    now = db.Column('time', db.DateTime())
    +result = await db.first(db.text(
    +    'SELECT now() AT TIME ZONE \'UTC\''
    +).columns(
    +    now,
    +).gino.load(
    +    ('now:', now)
    +).query)
    +print(*result)  # now: 2018-04-08 08:23:02.431847
    +
    +
    +

    Let’s get back to previous example. The second item in the tuple is a GINO +model class. As we’ve presented previously, it is interpreted into a +ModelLoader. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values.

    +
    +

    Tip

    +

    For a complex loader expression, the same row is given to all loaders, so +it doesn’t matter User.id is already used before the model loader.

    +
    +

    The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we’ll get to that later. Similar to +map(), the return value of the call will be the loaded result.

    +

    At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the '|' separator, it will show up as the third item +in every result returned by the query.

    +
    +
    +

    Many-to-One Relationship

    +

    A classic many-to-one relationship is also known as referencing - the model on +the “many” end keeps a single reference to the model on the “one” end. Although +GINO does not enforce it, usually people use a foreign key for the reference:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +

    So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:

    +
    async for child in Child.load(parent=Parent).gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    As you may have noticed, Child.load is exactly the shortcut to create +ModelLoader in the very first example. With some +additional keyword arguments, Child.load(parent=Parent) is still a +ModelLoader for Child, the model loader is at the +same time a query builder. It is identical to do this:

    +
    async for child in Child.load(parent=Parent).query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    The query dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The Loader simply forwarded unknown +attributes to its query, that’s why .query can +be omitted.

    +

    For ModelLoader, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using setattr(). For example, Parent is +interpreted as ModelLoader(Parent) which loads Parent instances, and +Parent instances are set as the parent attribute of the outer Child +instance.

    +
    +

    Warning

    +

    If multiple children references the same parent, then each child owns a +unique parent instance with identical values.

    +
    +
    +

    Tip

    +

    You don’t have to define parent attribute on Child. But if you do, +you gain the ability to customize how parent is stored or retrieved. For +example, let’s store the parent instance as _parent:

    +
    class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    _parent = None
    +
    +    @property
    +    def parent(self):
    +        return self._parent
    +
    +    @parent.setter
    +    def parent(self, value):
    +        self._parent = value
    +
    +
    +
    +

    The query builder works recursively. For ModelLoader, it +uses LEFT OUTER JOIN to connect the FROM clauses, in order to achieve +many-to-one scenario. The ON clause is determined automatically by foreign +keys. You can also customize the ON clause in case there is no foreign key +(a promise is a promise):

    +
    loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +async for child in loader.query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    And subloaders can be nested:

    +
    subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
    +
    +
    +

    By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values.

    +
    +
    +

    Self Referencing

    +
    +

    Warning

    +

    Experimental feature.

    +
    +

    Self referencing is usually used to create a tree-like structure. For example:

    +
    class Category(db.Model):
    +    __tablename__ = 'categories'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    +
    +
    +

    In order to load leaf categories with their parents, an alias is needed:

    +
    Parent = Category.alias()
    +
    +
    +

    Then the query would be something like this:

    +
    parents = db.select([Category.parent_id])
    +query = Category.load(parent=Parent.on(
    +    Category.parent_id == Parent.id
    +)).where(
    +    ~Category.id.in_(db.select([Category.alias().parent_id]))
    +)
    +async for c in query.gino.iterate():
    +    print(f'Leaf: {c.id}, Parent: {c.parent.id}')
    +
    +
    +

    The generated SQL looks like this:

    +
    SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id
    +  FROM categories LEFT OUTER JOIN categories AS categories_1
    +    ON categories.parent_id = categories_1.id
    + WHERE categories.id NOT IN (
    +           SELECT categories_2.parent_id
    +             FROM categories AS categories_2
    +       )
    +
    +
    +
    +
    +

    Other Relationships

    +

    GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    @children.setter
    +    def add_child(self, child):
    +        self._children.add(child)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child)).all()
    +
    +
    +

    Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the add_child setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +parent.add_child = new_child.

    +

    Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries.

    +

    GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you’ll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._child = None
    +
    +    @property
    +    def child(self):
    +        return self._child
    +
    +    @child.setter
    +    def child(self, child):
    +        self._child = child
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
    +
    +
    +

    Similarly, you can build many-to-many relationships in the same way:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    def add_child(self, child):
    +        self._children.add(child)
    +        child._parents.add(self)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._parents = set()
    +
    +    @property
    +    def parents(self):
    +        return self._parents
    +
    +
    +class ParentXChild(db.Model):
    +    __tablename__ = 'parents_x_children'
    +
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
    +
    +
    +query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
    +
    +
    +

    Likewise, there is for now no way to modify the relationships automatically, +you’ll have to manually create, delete or modify ParentXChild instances.

    +
    +
    +

    Advanced Usage of Loaders

    +

    You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For example, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Using alias to get ID-ascending pairs from the same table:

    +
    ua1 = User.alias()
    +ua2 = User.alias()
    +join_query = select([ua1, ua2]).where(ua1.id < ua2.id)
    +loader = ua1.load('id'), ua2.load('id')
    +result = await join_query.gino.load(loader).all()
    +print(result)  # e.g. [(1, 2), (1, 3), (2, 3)]
    +
    +
    +

    Potentially there could be a lot of different use cases of loaders. We’ll add +more inspiration here in the future.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/pool.html b/docs/en/1.0/how-to/pool.html new file mode 100644 index 0000000..e9a3021 --- /dev/null +++ b/docs/en/1.0/how-to/pool.html @@ -0,0 +1,214 @@ + + + + + + + + Connection Pool - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Connection Pool

    +

    Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +NullPool), and users can define their own pools. +The base class should be Pool.

    +

    To use non-default pools in raw GINO:

    +
    from gino.dialects.asyncpg import NullPool
    +create_engine('postgresql://...', pool_class=NullPool)
    +
    +
    +

    To use non-default pools in extensions (taking Sanic as an example):

    +
    from gino.dialects.asyncpg import NullPool
    +from gino.ext.sanic import Gino
    +
    +app = sanic.Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_KWARGS = dict(
    +    pool_class=NullPool,
    +)
    +db = Gino()
    +db.init_app(app)
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/schema.html b/docs/en/1.0/how-to/schema.html new file mode 100644 index 0000000..b492d1f --- /dev/null +++ b/docs/en/1.0/how-to/schema.html @@ -0,0 +1,416 @@ + + + + + + + + Schema Declaration - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Schema Declaration

    +

    There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy Table.

    +
    +

    GINO Engine

    +

    This is the minimized way to use GINO - using only +GinoEngine (and GinoConnection +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two.

    +

    For example, the table declaration is the same as SQLAlchemy core tutorial:

    +
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    +
    +metadata = MetaData()
    +
    +users = Table(
    +    'users', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('name', String),
    +    Column('fullname', String),
    +)
    +
    +addresses = Table(
    +    'addresses', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('user_id', None, ForeignKey('users.id')),
    +    Column('email_address', String, nullable=False)
    +)
    +
    +
    +
    +

    Note

    +

    When using GINO Engine only, it is usually your own business to create the +tables with either create_all() on a +normal non-async SQLAlchemy engine, or using Alembic. However it is still +possible to be done with GINO if it had to:

    +
    import gino
    +from gino.schema import GinoSchemaVisitor
    +
    +async def main():
    +    engine = await gino.create_engine('postgresql://...')
    +    await GinoSchemaVisitor(metadata).create_all(engine)
    +
    +
    +
    +

    Then, construct queries, in SQLAlchemy core too:

    +
    ins = users.insert().values(name='jack', fullname='Jack Jones')
    +
    +
    +

    So far, everything is still in SQLAlchemy. Now let’s get connected and execute +the insert:

    +
    async def main():
    +    engine = await gino.create_engine('postgresql://localhost/gino')
    +    conn = await engine.acquire()
    +    await conn.status(ins)
    +    print(await conn.all(users.select()))
    +    # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Here create_engine() creates a GinoEngine, +then acquire() checks out a +GinoConnection, and +status() executes the insert and returns the +status text. This works similarly as SQLAlchemy +execute() - they take the same parameters +but return a bit differently. There are also other similar query APIs:

    + +

    Please go to their API for more information.

    +
    +
    +

    GINO Core

    +

    In previous scenario, GinoEngine must not be set to +metadata.bind because it is not a +regular SQLAlchemy Engine thus it won’t work correctly. For this, GINO provides +a subclass of MetaData as Gino, +usually instantiated globally under the name of db. It can be used as a +normal MetaData still offering some conveniences:

    +
      +
    • It delegates most public types you can access on sqlalchemy

    • +
    • It works with both normal SQLAlchemy engine and asynchronous GINO engine

    • +
    • It exposes all query APIs on GinoConnection level

    • +
    • It injects two gino extensions on SQLAlchemy query clauses and schema +items, allowing short inline execution like users.select().gino.all()

    • +
    • It is also the entry for the third scenario, see later

    • +
    +

    Then we can achieve previous scenario with less code like this:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +users = db.Table(
    +    'users', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('name', db.String),
    +    db.Column('fullname', db.String),
    +)
    +
    +addresses = db.Table(
    +    'addresses', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('user_id', None, db.ForeignKey('users.id')),
    +    db.Column('email_address', db.String, nullable=False)
    +)
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await users.insert().values(
    +            name='jack',
    +            fullname='Jack Jones',
    +        ).gino.status()
    +        print(await users.select().gino.all())
    +        # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but sqlalchemy seems +never imported. This is useful when ORM is unwanted.

    +
    +

    Tip

    +

    asyncpgsa does the same thing, +but in a conceptually reversed way - instead of having asyncpg work for +SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that +way too because GINO is inspired by asyncpgsa). Either way works fine, it’s +just a matter of taste of whose API style to use, SQLAlchemy or asyncpg.

    +
    +
    +
    +

    GINO ORM

    +

    If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    fullname = db.Column(db.String)
    +
    +
    +class Address(db.Model):
    +    __tablename__ = 'addresses'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    user_id = db.Column(None, db.ForeignKey('users.id'))
    +    email_address = db.Column(db.String, nullable=False)
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await User.create(name='jack', fullname='Jack Jones')
    +        print(await User.query.gino.all())
    +        # Outputs: [<User object at 0x10a8ba860>]
    +
    +
    +
    +

    Important

    +

    The __tablename__ is a mandatory field to define a concrete model.

    +
    +

    As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in db. The class style is just +more declarative. Instead of users.c.name, you can now access the column by +User.name. The implicitly created Table is +available at User.__table__ and Address.__table__. You can use anything +that works in GINO core here.

    +
    +

    Note

    +

    Column names can be different as a class property and database column. +For example, name can be declared as +nickname = db.Column('name', db.Unicode(), default='noname'). In this +example, User.nickname is used to access the column, while in database, +the column name is name.

    +

    What’s worth mentioning is where raw SQL statements are used, or +TableClause is involved, like User.insert(), the original name is +required to be used, because in this case, GINO has no knowledge about the +mappings.

    +
    +
    +

    Tip

    +

    db.Model is a dynamically created parent class for your models. It is +associated with the db on initialization, therefore the table is put in +the very db when you declare your model class.

    +
    +

    Things become different when it comes to CRUD. You can use model level methods +to directly create() a model instance, instead of +inserting a new row. Or delete() a model instance +without needing to specify the where clause manually. Query returns model +instances instead of RowProxy, and row values are +directly available as attributes on model instances. See also: +CRUD.

    +

    After all, GinoEngine is always in use. Next let’s dig +more into it.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/how-to/transaction.html b/docs/en/1.0/how-to/transaction.html new file mode 100644 index 0000000..02afeed --- /dev/null +++ b/docs/en/1.0/how-to/transaction.html @@ -0,0 +1,300 @@ + + + + + + + + Transaction - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Transaction

    +

    It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an await will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it.

    +
    +

    Basic usage

    +

    Transactions belong to GinoConnection. The most common +way to use transactions is through an async with statement:

    +
    async with connection.transaction() as tx:
    +    await connection.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    This guarantees a transaction is opened when entering the async with block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at raw_transaction, but in +most cases you don’t need to touch it.

    +

    GINO provides two convenient shortcuts to end the transaction early:

    + +

    They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the async with block after +raise_commit() or +raise_rollback() is skipped. The +internal exception is inherited from BaseException so that normal try +... except Exception block can’t trap it. This exception stops propagating at +the end of async with block, so you don’t need to worry about handling it.

    +

    Transactions can also be started on a GinoEngine:

    +
    async with engine.transaction() as tx:
    +    await engine.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    Here a GinoConnection is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The GinoConnection instance is accessible at +tx.connection. Other than +that, everything else is the same.

    +
    +

    Important

    +

    The implicit connection is by default borrowed with reuse=True. That +means using transaction() of +GinoEngine within a connection context is the same as +calling transaction() of the current +connection without having to reference it, no separate connection shall be +created.

    +
    +

    Similarly, if your Gino instance has a bind, you may also do +the same on it:

    +
    async with db.transaction() as tx:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +
    +
    +

    Nested Transactions

    +

    Transactions can be nested, nested transaction will create a savepoint as for +now on asyncpg. A similar example from asyncpg doc:

    +
    async with connection.transaction() as tx1:
    +    await connection.status('CREATE TABLE mytab (a int)')
    +
    +    # Create a nested transaction:
    +    async with connection.transaction() as tx2:
    +        await connection.status('INSERT INTO mytab (a) VALUES (1), (2)')
    +        # Rollback the nested transaction:
    +        tx2.raise_rollback()
    +
    +    # Because the nested transaction was rolled back, there
    +    # will be nothing in `mytab`.
    +    assert await connection.all('SELECT a FROM mytab') == []
    +
    +
    +

    As you can see, the raise_rollback() +breaks only the async with block of the specified tx2, the outer +transaction tx1 just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won’t trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate.

    +

    Because of the default reusing behavior, transactions on engine or db +follows the same nesting rules. Please see +GinoTransaction for more information.

    +
    +
    +

    Manual Control

    +

    Other than using async with, you can also manually control the +transaction:

    +
    tx = await connection.transaction()
    +try:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    You can’t use raise_commit() or +raise_rollback() here, similarly it is +prohibited to use commit() and +rollback() in an async with block.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/index.html b/docs/en/1.0/index.html new file mode 100644 index 0000000..701390f --- /dev/null +++ b/docs/en/1.0/index.html @@ -0,0 +1,228 @@ + + + + + + + + Welcome to GINO’s documentation! - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Welcome to GINO’s documentation!

    +PyPI Release Version +GitHub Workflow Status for tests +Codacy coverage +Dependabot +

    GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy core for Python asyncio. Now (early 2020) GINO supports only one +dialect asyncpg.

    + + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/objects.inv b/docs/en/1.0/objects.inv new file mode 100644 index 0000000..8938fe2 Binary files /dev/null and b/docs/en/1.0/objects.inv differ diff --git a/docs/en/1.0/py-modindex.html b/docs/en/1.0/py-modindex.html new file mode 100644 index 0000000..18dcbf9 --- /dev/null +++ b/docs/en/1.0/py-modindex.html @@ -0,0 +1 @@ +

    domainindex.html

    \ No newline at end of file diff --git a/docs/en/1.0/reference.html b/docs/en/1.0/reference.html new file mode 100644 index 0000000..c41ad01 --- /dev/null +++ b/docs/en/1.0/reference.html @@ -0,0 +1,327 @@ + + + + + + + + Reference - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Reference

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api.html b/docs/en/1.0/reference/api.html new file mode 100644 index 0000000..381fe91 --- /dev/null +++ b/docs/en/1.0/reference/api.html @@ -0,0 +1,237 @@ + + + + + + + + API Reference - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.aiocontextvars.html b/docs/en/1.0/reference/api/gino.aiocontextvars.html new file mode 100644 index 0000000..70263b3 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.aiocontextvars.html @@ -0,0 +1,213 @@ + + + + + + + + gino.aiocontextvars module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.aiocontextvars module

    +
    +
    +gino.aiocontextvars.patch_asyncio()
    +

    Patches asyncio to support contextvars.

    +

    This is automatically called when gino is imported. If Python version is 3.7 +or greater, this function is a no-op.

    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.api.html b/docs/en/1.0/reference/api/gino.api.html new file mode 100644 index 0000000..b78c130 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.api.html @@ -0,0 +1,584 @@ + + + + + + + + gino.api module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.api module

    +
    +
    +class gino.api.Gino(bind=None, model_classes=None, query_ext=True, schema_ext=True, ext=True, **kwargs)
    +

    Bases: sqlalchemy.sql.schema.MetaData

    +

    All-in-one API class of GINO, providing several shortcuts.

    +

    This class is a subclass of SQLAlchemy +MetaData, therefore its instances can be used +as a normal MetaData object, e.g. used in +Alembic. In usual cases, you would +want to define one global Gino instance, usually under the name +of db, representing the database used in your application.

    +

    You may define tables in the official way +SQLAlchemy core recommended, but more often in GINO we define model classes +with db.Model as their parent class to represent tables, for its +objective interface and CRUD operations. Please read CRUD +for more information.

    +

    For convenience, Gino instance delegated all properties publicly +exposed by sqlalchemy, so that you can define tables / models +without importing sqlalchemy:

    +
    id = db.Column(db.BigInteger(), primary_key=True)
    +
    +
    +

    Similar to MetaData, a Gino object +can bind to a GinoEngine instance, hereby allowing +“implicit execution” through the gino +extension on Executable or +SchemaItem constructs:

    +
    await User.query.gino.first()
    +await db.gino.create_all()
    +
    +
    +

    Differently, GINO encourages the use of implicit execution and manages +transactional context correctly.

    +

    Binding to a connection object is not supported.

    +

    To set a bind property, you can simply set your +GinoEngine object on db.bind, or +set it to None to unbind. However, the creation of engine usually +happens at the same time. Therefore, GINO provided several convenient ways +doing so:

    +
      +
    1. with_bind() returning an asynchronous context manager:

      +
      async with db.with_bind('postgresql://...') as engine:
      +
      +
      +
    2. +
    3. set_bind() and pop_bind():

      +
      engine = await db.set_bind('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    4. +
    5. Directly await on Gino instance:

      +
      db = await gino.Gino('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    6. +
    +
    +

    Note

    +

    SQLAlchemy allows creating the engine by:

    +
    metadata.bind = 'postgresql://...'
    +
    +
    +

    While in GINO this only sets a string to bind, because +creating an engine requires await, which is exactly what +set_bind() does.

    +
    +

    At last, Gino delegates all query APIs on the bound +GinoEngine.

    +
    +
    +property Model
    +

    Declarative base class for models, subclass of +gino.declarative.Model. Defining subclasses of this class will +result new tables added to this Gino metadata.

    +
    + +
    +
    +acquire(*args, **kwargs)
    +

    A delegate of GinoEngine.acquire().

    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.all().

    +
    + +
    +
    +property bind
    +

    An GinoEngine to which this Gino is bound.

    +

    This is a simple property with no getter or setter hook - what you set +is what you get. To achieve the same result as it is in SQLAlchemy - +setting a string or URL and getting an +engine instance, use set_bind() (or await on this +Gino object after setting a string or +URL).

    +
    + +
    +
    +compile(elem, *multiparams, **params)
    +

    A delegate of GinoEngine.compile().

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.first().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.iterate().

    +
    + +
    +
    +model_base_classes = (<class 'gino.crud.CRUDModel'>,)
    +

    Overridable default model classes to build the Model.

    +

    Default is (CRUDModel, ).

    +
    + +
    +
    +no_delegate = {'create_engine', 'engine_from_config'}
    +

    A set of symbols from sqlalchemy which is not delegated by +Gino.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one_or_none().

    +
    + +
    +
    +pop_bind()
    +

    Unbind self, and return the bound engine.

    +

    This is usually used in a chained call to close the engine:

    +
    await db.pop_bind().close()
    +
    +
    +
    +
    Returns
    +

    GinoEngine or None if self is not bound.

    +
    +
    +
    + +
    +
    +query_executor
    +

    The overridable gino extension class on +Executable.

    +

    This class will be set as the getter method of the property gino on +Executable and its subclasses, if +ext and query_ext arguments are both True. Default is +GinoExecutor.

    +

    alias of GinoExecutor

    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.scalar().

    +
    + +
    +
    +schema_visitor
    +

    alias of gino.schema.GinoSchemaVisitor

    +
    + +
    +
    +async set_bind(bind, loop=None, **kwargs)
    +

    Bind self to the given GinoEngine and return it.

    +

    If the given bind is a string or +URL, all arguments will be sent to +create_engine() to create a new engine, and return it.

    +
    +
    Returns
    +

    GinoEngine

    +
    +
    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.status().

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    A delegate of GinoEngine.transaction().

    +
    + +
    +
    +with_bind(bind, loop=None, **kwargs)
    +

    Shortcut for set_bind() and pop_bind() plus closing engine.

    +

    This method accepts the same arguments of +create_engine(). This allows inline creating an engine and +binding self on enter, and unbinding self and closing the engine on +exit:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # play with engine
    +
    +
    +
    +
    Returns
    +

    An asynchronous context manager.

    +
    +
    +
    + +
    + +
    +
    +class gino.api.GinoExecutor(query)
    +

    Bases: object

    +

    The default gino extension on +Executable constructs for implicit +execution.

    +

    Instances of this class are created when visiting the gino property of +Executable instances (also referred as +queries or clause elements), for example:

    +
    await User.query.gino.first()
    +
    +
    +

    This allows GINO to add the asynchronous query APIs (all(), +first(), one(), one_or_none(), scalar(), +status(), iterate()) to SQLAlchemy query clauses without +messing up with existing synchronous ones. +Calling these asynchronous query APIs has the same restriction - the +relevant metadata (the Gino instance) must be bound to an engine, +or an AttributeError will be raised.

    +
    +

    Note

    +

    Executable clause elements that are completely irrelevant with any +table - for example db.select([db.text('now()')]) - has no +metadata, hence no engine. Therefore, this will always fail:

    +
    await db.select([db.text('now()')]).gino.scalar()
    +
    +
    +

    You should use conn.scalar(), +engine.scalar() or even +db.scalar() in this case.

    +
    +
    +
    +async all(*multiparams, **params)
    +

    Returns engine.all() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async first(*multiparams, **params)
    +

    Returns engine.first() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +iterate(*multiparams, **params)
    +

    Returns engine.iterate() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +load(value)
    +

    Shortcut to set execution option loader in a chaining call.

    +

    For example to load Book instances with their authors:

    +
    query = Book.join(User).select()
    +books = await query.gino.load(Book.load(author=User)).all()
    +
    +
    +

    Read execution_options() for more +information.

    +
    + +
    +
    +model(model)
    +

    Shortcut to set execution option model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async one(*multiparams, **params)
    +

    Returns engine.one() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async one_or_none(*multiparams, **params)
    +

    Returns engine.one_or_none() +with this query as the first argument, and other arguments followed, +where engine is the GinoEngine to which the +metadata (Gino) is bound, while metadata is found in this +query.

    +
    + +
    +
    +property query
    +

    Get back the chained Executable.

    +

    In a chained query calls, occasionally the previous query clause is +needed after a .gino. chain, you can use .query. to resume the +chain back. For example:

    +
    await User.query.gino.model(FOUser).query.where(...).gino.all()
    +
    +
    +
    + +
    +
    +return_model(switch)
    +

    Shortcut to set execution option return_model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async scalar(*multiparams, **params)
    +

    Returns engine.scalar() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async status(*multiparams, **params)
    +

    Returns engine.status() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +timeout(timeout)
    +

    Shortcut to set execution option timeout in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.crud.html b/docs/en/1.0/reference/api/gino.crud.html new file mode 100644 index 0000000..0462674 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.crud.html @@ -0,0 +1,650 @@ + + + + + + + + gino.crud module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.crud module

    +
    +
    +class gino.crud.Alias(model, *args, **kwargs)
    +

    Bases: object

    +

    Experimental proxy for table alias on model.

    +
    +
    +distinct(*columns)
    +
    + +
    +
    +load(*column_names, **relationships)
    +
    + +
    +
    +on(on_clause)
    +
    + +
    + +
    +
    +class gino.crud.CRUDModel(**values)
    +

    Bases: gino.declarative.Model

    +

    The base class for models with CRUD support.

    +

    Don’t inherit from this class directly, because it has no metadata. Use +db.Model instead.

    +
    +
    +classmethod alias(*args, **kwargs)
    +

    Experimental proxy for table alias on model.

    +
    + +
    +
    +append_where_primary_key(q)
    +

    Append where clause to locate this model instance by primary on the +given query, and return the new query.

    +

    This is mostly used internally in GINO, but also available for such +usage:

    +
    await user.append_where_primary_key(User.query).gino.first()
    +
    +
    +

    which is identical to:

    +
    await user.query.gino.first()
    +
    +
    +
    +

    Deprecated since version 0.7.6: Use lookup() instead.

    +
    +
    + +
    +
    +create(bind=None, timeout=<object object>, **values)
    +

    This create behaves a bit different on model classes compared to model +instances.

    +

    On model classes, create will create a new model instance and insert +it into database. On model instances, create will just insert the +instance into the database.

    +

    Under the hood create() uses INSERT ... RETURNING ... to +create the new model instance and load it with database default data if +not specified.

    +

    Some examples:

    +
    user1 = await User.create(name='fantix', age=32)
    +user2 = User(name='gino', age=42)
    +await user2.create()
    +
    +
    +
    +
    Parameters
    +
      +
    • bind – A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    • values – Keyword arguments are pairs of attribute names and their +initial values. Only available when called on a model class.

    • +
    +
    +
    Returns
    +

    The instance of this model class (newly created or existing).

    +
    +
    +
    + +
    +
    +delete
    +

    Similar to update(), this delete is also different on model +classes than on model instances.

    +

    On model classes delete is an attribute of type +Delete for massive deletes, for +example:

    +
    await User.delete.where(User.enabled.is_(False)).gino.status()
    +
    +
    +

    Similarly you can add a returning() +clause to the query and it shall return the deleted rows as model objects.

    +

    And on model instances, delete() is a method to remove the +corresponding row in the database of this model instance. and returns the +status returned from the database:

    +
    print(await user.delete())  # e.g. prints DELETE 1
    +
    +
    +
    +

    Note

    +

    delete() only removes the row from database, it does not affect the +current model instance.

    +
    +
    +
    Parameters
    +
      +
    • bind – An optional GinoEngine if current +metadata (Gino) has no bound engine, or specifying a +different GinoEngine to execute the DELETE.

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    +
    + +
    +
    +classmethod distinct(*columns)
    +

    Experimental loader feature to yield only distinct instances by given +columns.

    +
    + +
    +
    +async classmethod get(ident, bind=None, timeout=<object object>)
    +

    Get an instance of this model class by primary key.

    +

    For example:

    +
    user = await User.get(request.args.get('user_id'))
    +
    +
    +
    +
    Parameters
    +
      +
    • ident – Value of the primary key. For composite primary keys this +should be a tuple of values for all keys in database order, or a dict +of names (or position numbers in database order starting from zero) +of all primary keys to their values.

    • +
    • bind – A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    Returns
    +

    An instance of this model class, or None if no such row.

    +
    +
    +
    + +
    +
    +classmethod in_query(query)
    +

    Convenient method to get a Model object when using subqueries.

    +

    Though with filters and aggregations, subqueries often return same +columns as the original table, but SQLAlchemy could not recognize them +as the columns are in subqueries, so technically they’re columns in the +new “table”.

    +

    With this method, the columns are loaded into the original models when +being used in subqueries. For example:

    +
    query = query.alias('users')
    +MyUser = User.in_query(query)
    +
    +loader = MyUser.distinct(User1.id).load()
    +users = await query.gino.load(loader).all()
    +
    +
    +
    + +
    +
    +classmethod load(*column_names, **relationships)
    +

    Populates a loader.Loader instance to be used by the +loader execution option in order to customize the loading behavior +to load specified fields into instances of this model.

    +

    The basic usage of this method is to provide the loader execution +option (if you are looking for reloading +the instance from database, check get() or query) for a +given query.

    +

    This method takes both positional arguments and keyword arguments with +very different meanings. The positional arguments should be column +names as strings, specifying only these columns should be loaded into +the model instance (other values are discarded even if they are +retrieved from database). Meanwhile, the keyword arguments should be +loaders for instance attributes. For example:

    +
    u = await User.query.gino.load(User.load('id', 'name')).first()
    +
    +
    +
    +

    Tip

    +

    gino.load is a shortcut for setting the execution option +loader.

    +
    +

    This will populate a User instance with only id and name +values, all the rest are simply None even if the query actually +returned all the column values.

    +
    q = User.join(Team).select()
    +u = await q.gino.load(User.load(team=Team)).first()
    +
    +
    +

    This will load two instances of model User and Team, returning +the User instance with u.team set to the Team instance.

    +

    Both positional and keyword arguments can be used ath the same time. If +they are both omitted, like Team.load(), it is equivalent to just +Team as a loader.

    +

    Additionally, a loader.Loader instance can also be used to +generate queries, as its structure is usually the same as the query:

    +
    u = await User.load(team=Team).query.gino.first()
    +
    +
    +

    This generates a query like this:

    +
    SELECT users.xxx, ..., teams.xxx, ...
    +  FROM users LEFT JOIN teams
    +    ON ...
    +
    +
    +

    The Loader delegates attributes on the query, so +.query can be omitted. The LEFT JOIN is built-in behavior, +while the ON clause is generated based on foreign key. If there is +no foreign key, or the condition should be customized, you can use +this:

    +
    u = await User.load(
    +    team=Team.on(User.team_id == Team.id)).gino.first()
    +
    +
    +

    And you can use both load() and on() at the same time +in a chain, in whatever order suits you.

    +
    +

    See also

    +

    execution_options()

    +
    +
    + +
    +
    +lookup()
    +

    Generate where-clause expression to locate this model instance.

    +

    By default this method uses current values of all primary keys, and you +can override it to behave differently. Most instance-level CRUD +operations depend on this method internally. Particularly while +lookup() is called in update(), the where condition is +used in UpdateRequest.apply(), so that queries like UPDATE ... +SET id = NEW WHERE id = OLD could work correctly.

    +
    +
    Returns
    +

    +
    +
    +
    +

    New in version 0.7.6.

    +
    +
    + +
    +
    +classmethod none_as_none(enabled=True)
    +
    + +
    +
    +classmethod on(on_clause)
    +

    Customize the on-clause for the auto-generated outer join query.

    +
    +

    Note

    +

    This has no effect when provided as the loader execution option +for a given query.

    +
    +
    +

    See also

    +

    load()

    +
    +
    + +
    +
    +query
    +

    Get a SQLAlchemy query clause of the table behind this model. This equals +to sqlalchemy.select([self.__table__]). If this attribute is retrieved on a +model instance, then a where clause to locate this instance by its primary +key is appended to the returning query clause. This model type is set as +the execution option model in the returning clause, so by default the +query yields instances of this model instead of database rows.

    +
    + +
    +
    +select()
    +

    Build a query to retrieve only specified columns from this table.

    +

    This method accepts positional string arguments as names of attributes to +retrieve, and returns a Select for +query. The returning query object is always set with two execution options:

    +
      +
    1. model is set to this model type

    2. +
    3. return_model is set to False

    4. +
    +

    So that by default it always return rows instead of model instances, while +column types can be inferred correctly by the model option.

    +

    For example:

    +
    async for row in User.select('id', 'name').gino.iterate():
    +    print(row['id'], row['name'])
    +
    +
    +

    If select() is invoked on a model instance, then a WHERE clause +to locate this instance by its primary key is appended to the returning +query clause. This is useful when you want to retrieve a latest value of a +field on current model instance from database:

    +
    db_age = await user.select('age').gino.scalar()
    +
    +
    +
    +

    See also

    +

    execution_options()

    +
    +
    + +
    +
    +to_dict()
    +

    Convenient method to generate a dict from this model instance.

    +

    Keys will be attribute names, while values are loaded from memory (not +from database). If there are JSONProperty +attributes in this model, their source JSON field will not be included +in the returning dict - instead the JSON attributes will be.

    +
    +

    See also

    +

    json_support

    +
    +
    + +
    +
    +update
    +

    This update behaves quite different on model classes rather than model +instances.

    +

    On model classes, update is an attribute of type +Update for massive updates, for +example:

    +
    await User.update.values(enabled=True).where(...).gino.status()
    +
    +
    +

    Like query, the update query also has the model execution +option of this model, so if you use the +returning() clause, the query shall +return model objects.

    +

    However on model instances, update() is a method which accepts keyword +arguments only and returns an UpdateRequest to update this single +model instance. The keyword arguments are pairs of attribute names and new +values. This is the same as UpdateRequest.update(), feel free to +read more about it. A normal usage example would be like this:

    +
    await user.update(name='new name', age=32).apply()
    +
    +
    +

    Here, the await ... apply() executes the +actual UPDATE SQL in the database, while user.update() only makes +changes in the memory, and collect all changes into an +UpdateRequest instance.

    +
    + +
    + +
    +
    +class gino.crud.QueryModel
    +

    Bases: type

    +

    Metaclass of Model classes used for subqueries.

    +
    + +
    +
    +class gino.crud.UpdateRequest(instance: gino.crud.CRUDModel)
    +

    Bases: object

    +

    A collection of attributes and their new values to update on one model +instance.

    +

    UpdateRequest instances are created by CRUDModel.update, +don’t instantiate manually unless required. Every UpdateRequest +instance is bound to one model instance, all updates are for that one +specific model instance and its database row.

    +
    +
    +async apply(bind=None, timeout=<object object>)
    +

    Apply pending updates into database by executing an UPDATE SQL.

    +
    +
    Parameters
    +
      +
    • bind – A GinoEngine to execute the SQL, or +None (default) to use the bound engine in the metadata.

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    Returns
    +

    self for chaining calls.

    +
    +
    +
    + +
    +
    +update(**values)
    +

    Set given attributes on the bound model instance, and add them into +the update collections for apply().

    +

    Given keyword-only arguments are pairs of attribute names and values to +update. This is not a coroutine, calling update() will have +instant effect on the bound model instance - its in-memory values will +be updated immediately. Therefore this can be used individually as a +shortcut to update several attributes in a batch:

    +
    user.update(age=32, disabled=True)
    +
    +
    +

    update() returns self for chaining calls to either +apply() or another update(). If one attribute is updated +several times by the same UpdateRequest, then only the last +value is remembered for apply().

    +

    Updated values can be SQLAlchemy expressions, for example an atomic +increment for user balance looks like this:

    +
    await user.update(balance=User.balance + 100).apply()
    +
    +
    +
    +

    Note

    +

    Expression values will not affect the in-memory attribute value on +update() before apply(), because it has no knowledge +of the latest value in the database. After apply() the new +value will be automatically reloaded from database with +RETURNING clause.

    +
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.declarative.html b/docs/en/1.0/reference/api/gino.declarative.html new file mode 100644 index 0000000..b6c6c29 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.declarative.html @@ -0,0 +1,382 @@ + + + + + + + + gino.declarative module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.declarative module

    +
    +
    +class gino.declarative.ColumnAttribute(prop_name, column)
    +

    Bases: object

    +

    The type of the column wrapper attributes on GINO models.

    +

    This is the core utility to enable GINO models so that:

    +
      +
    • Accessing a column attribute on a model class returns the column itself

    • +
    • Accessing a column attribute on a model instance returns the value for that column

    • +
    +

    This utility is customizable by defining __attr_factory__ in the model class.

    +
    + +
    +
    +class gino.declarative.InvertDict(*args, **kwargs)
    +

    Bases: dict

    +

    A custom dict that allows getting keys by values.

    +

    Used internally by Model.

    +
    +
    +invert_get(value, default=None)
    +

    Get key by value.

    +
    +
    Parameters
    +
      +
    • value – A value in this dict.

    • +
    • default – If specified value doesn’t exist, return default.

    • +
    +
    +
    Returns
    +

    The corresponding key if the value is found, or default otherwise.

    +
    +
    +
    + +
    + +
    +
    +class gino.declarative.Model
    +

    Bases: object

    +

    The base class of GINO models.

    +

    This is not supposed to be sub-classed directly, declarative_base() should +be used instead to generate a base model class with a given +MetaData. By defining subclasses of a model, instances +of sqlalchemy.schema.Table will be created and added to the bound +MetaData. The columns of the +Table instance are defined as +Column attributes:

    +
    from sqlalchemy import MetaData, Column, Integer, String
    +from gino.declarative import declarative_base
    +
    +Model = declarative_base()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = Column(Integer(), primary_key=True)
    +    name = Column(String())
    +
    +
    +

    The name of the columns are automatically set using the attribute name.

    +

    An instance of a model will maintain a memory storage for values of all the defined +column attributes. You can access these values by the same attribute name, or update +with new values, just like normal Python objects:

    +
    u = User()
    +assert u.name is None
    +
    +u.name = "daisy"
    +assert u.name == "daisy"
    +
    +
    +
    +

    Note

    +

    Accessing column attributes on a model instance will NOT trigger any database +operation.

    +
    +

    Constraint and Index are +also allowed as model class attributes. Their attribute names are not used.

    +

    A concrete model class can be used as a replacement of the +Table it reflects in SQLAlchemy queries. The model class +is also iterable, yielding all the Column instances +defined in the model.

    +

    Other built-in class attributes:

    +
      +
    • __metadata__

      +

      This is supposed to be set by declarative_base() and used only during +subclass construction. Still, this can be treated as a read-only attribute to +find out which MetaData this model is bound to.

      +
    • +
    • __tablename__

      +

      This is a required attribute to define a concrete model, meaning a +sqlalchemy.schema.Table instance will be created, added to the bound +MetaData and set to the class attribute __table__. +Not defining __tablename__ will result in an abstract model - no table +instance will be created, and instances of an abstract model are meaningless.

      +
    • +
    • __table__

      +

      This should usually be treated as an auto-generated read-only attribute storing +the sqlalchemy.schema.Table instance.

      +
    • +
    • __attr_factory__

      +

      An attribute factory that is used to wrap the actual +Column instance on the model class, so that the access +to the column attributes on model instances is redirected to the in-memory value +store. The default factory is ColumnAttribute, can be override.

      +
    • +
    • __values__

      +

      The internal in-memory value store as a dict, only available on model +instances. Accessing column attributes is equivalent to accessing __values__.

      +
    • +
    +
    + +
    +
    +gino.declarative.declarative_base(metadata, model_classes=(<class 'gino.declarative.Model'>, ), name='Model')
    +

    Create a base GINO model class for declarative table definition.

    +
    +
    Parameters
    +
      +
    • metadata – A MetaData instance to contain the +tables.

    • +
    • model_classes – Base class(es) of the base model class to be created. Default: +Model.

    • +
    • name – The class name of the base model class to be created. Default: +Model.

    • +
    +
    +
    Returns
    +

    A new base model class.

    +
    +
    +
    + +
    +
    +gino.declarative.declared_attr(m)
    +

    Mark a class-level method as a factory of attribute.

    +

    This is intended to be used as decorators on class-level methods of a +Model class. When initializing the class as well as its +subclasses, the decorated factory method will be called for each class, the +returned result will be set on the class in place of the factory method +under the same name.

    +

    @declared_attr is implemented differently than +declared_attr of SQLAlchemy, but they +are both more often used on mixins to dynamically declare indices or +constraints (also works for column and __table_args__, or even normal +class attributes):

    +
    class TrackedMixin:
    +    created = db.Column(db.DateTime(timezone=True))
    +
    +    @db.declared_attr
    +    def unique_id(cls):
    +        return db.Column(db.Integer())
    +
    +    @db.declared_attr
    +    def unique_constraint(cls):
    +        return db.UniqueConstraint('unique_id')
    +
    +    @db.declared_attr
    +    def poly(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.Column(db.Unicode())
    +
    +    @db.declared_attr
    +    def __table_args__(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.UniqueConstraint('poly'),
    +
    +
    +
    +

    Note

    +

    This doesn’t work if the model already had a __table__.

    +
    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.dialects.asyncpg.html b/docs/en/1.0/reference/api/gino.dialects.asyncpg.html new file mode 100644 index 0000000..082c144 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.dialects.asyncpg.html @@ -0,0 +1,590 @@ + + + + + + + + gino.dialects.asyncpg module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.asyncpg module

    +
    +
    +class gino.dialects.asyncpg.AsyncEnum(*enums, **kw)
    +

    Bases: sqlalchemy.dialects.postgresql.base.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCompiler(dialect, statement, column_keys=None, inline=False, **kwargs)
    +

    Bases: sqlalchemy.dialects.postgresql.base.PGCompiler

    +
    +
    +property bindtemplate
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCursor(context, cursor)
    +

    Bases: gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDBAPI
    +

    Bases: gino.dialects.base.BaseDBAPI

    +
    +
    +Error = (<class 'asyncpg.exceptions._base.PostgresError'>, <class 'asyncpg.exceptions._base.InterfaceError'>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDialect(*args, **kwargs)
    +

    Bases: sqlalchemy.dialects.postgresql.base.PGDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.sql.sqltypes.ARRAY'>: <class 'sqlalchemy.dialects.postgresql.array.ARRAY'>, <class 'sqlalchemy.sql.sqltypes.Interval'>: <class 'sqlalchemy.dialects.postgresql.base.INTERVAL'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'gino.dialects.asyncpg.AsyncpgJSONPathType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.postgresql.json.JSON'>, <class 'sqlalchemy.dialects.postgresql.base.ENUM'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.asyncpg.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of AsyncpgDBAPI

    +
    + +
    +
    +driver = 'asyncpg'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of AsyncpgExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given an asyncpg connection, return its isolation level.

    +
    + +
    +
    +async has_schema(connection, schema)
    +
    + +
    +
    +async has_sequence(connection, sequence_name, schema=None)
    +

    Check the existence of a particular sequence in the database.

    +

    Given a _engine.Connection object and a string +sequence_name, return True if the given sequence exists in +the database, False otherwise.

    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +async has_type(connection, type_name, schema=None)
    +
    + +
    +
    +init_kwargs = {'command_timeout', 'connection_class', 'database', 'host', 'init', 'loop', 'max_cacheable_statement_size', 'max_cached_statement_lifetime', 'max_inactive_connection_lifetime', 'max_queries', 'max_size', 'min_size', 'passfile', 'password', 'port', 'server_settings', 'setup', 'ssl', 'statement_cache_size', 'timeout', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument “conn” which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The “do_on_connect” callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    Returns
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    See also

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given an asyncpg connection, set its isolation level.

    +
    + +
    +
    +statement_compiler
    +

    alias of AsyncpgCompiler

    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgExecutionContext
    +

    Bases: gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.postgresql.base.PGExecutionContext

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgIterator(context, iterator)
    +

    Bases: object

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgJSONPathType
    +

    Bases: sqlalchemy.dialects.postgresql.json.JSONPathType

    +
    +
    +bind_processor(dialect)
    +

    Return a conversion function for processing bind values.

    +

    Returns a callable which will receive a bind parameter value +as the sole positional argument and will return a value to +send to the DB-API.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +

    dialect – Dialect instance in use.

    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.DBAPICursor(dbapi_conn)
    +

    Bases: gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.GinoNullType
    +

    Bases: sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +
      +
    • dialect – Dialect instance in use.

    • +
    • coltype – DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.NullPool(url, loop, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.Pool(url, loop, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.PreparedStatement(prepared, clause=None)
    +

    Bases: gino.dialects.base.PreparedStatement

    +
    + +
    +
    +class gino.dialects.asyncpg.Transaction(tx)
    +

    Bases: gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.dialects.base.html b/docs/en/1.0/reference/api/gino.dialects.base.html new file mode 100644 index 0000000..3e4d14e --- /dev/null +++ b/docs/en/1.0/reference/api/gino.dialects.base.html @@ -0,0 +1,449 @@ + + + + + + + + gino.dialects.base module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.base module

    +
    +
    +class gino.dialects.base.AsyncDialectMixin
    +

    Bases: object

    +
    +
    +compile(elem, *multiparams, **params)
    +
    + +
    +
    +cursor_cls
    +

    alias of DBAPICursor

    +
    + +
    +
    +classmethod dbapi()
    +
    + +
    +
    +dbapi_class
    +

    alias of BaseDBAPI

    +
    + +
    +
    +async init_pool(url, loop)
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.base.BaseDBAPI
    +

    Bases: object

    +
    +
    +static Binary(x)
    +
    + +
    +
    +Error
    +

    alias of builtins.Exception

    +
    + +
    +
    +paramstyle = 'numeric'
    +
    + +
    + +
    +
    +class gino.dialects.base.Cursor
    +

    Bases: object

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.base.DBAPICursor
    +

    Bases: object

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +execute(statement, parameters)
    +
    + +
    +
    +executemany(statement, parameters)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.base.ExecutionContextOverride
    +

    Bases: object

    +
    +
    +get_result_proxy()
    +
    + +
    +
    +loader
    +
    + +
    +
    +model
    +
    + +
    +
    +process_rows(rows, return_model=True)
    +
    + +
    +
    +return_model
    +
    + +
    +
    +timeout
    +
    + +
    + +
    +
    +class gino.dialects.base.Pool
    +

    Bases: object

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.base.PreparedStatement(clause=None)
    +

    Bases: object

    +
    +
    +async all(*multiparams, **params)
    +
    + +
    +
    +async first(*multiparams, **params)
    +
    + +
    +
    +iterate(*params, **kwargs)
    +
    + +
    +
    +async scalar(*multiparams, **params)
    +
    + +
    +
    +async status(*multiparams, **params)
    +
    + +
    + +
    +
    +class gino.dialects.base.Transaction
    +

    Bases: object

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.dialects.html b/docs/en/1.0/reference/api/gino.dialects.html new file mode 100644 index 0000000..ec7f8dd --- /dev/null +++ b/docs/en/1.0/reference/api/gino.dialects.html @@ -0,0 +1,223 @@ + + + + + + + + gino.dialects package - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects package

    + +
    +

    Module contents

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.engine.html b/docs/en/1.0/reference/api/gino.engine.html new file mode 100644 index 0000000..e4b9927 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.engine.html @@ -0,0 +1,715 @@ + + + + + + + + gino.engine module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.engine module

    +
    +
    +class gino.engine.GinoConnection(dialect, sa_conn, stack=None)
    +

    Bases: object

    +

    Represents an actual database connection.

    +

    This is the root of all query API like all(), first(), +one(), one_or_none(), scalar() or status(), +those on engine or query are simply wrappers of methods in this class.

    +

    Usually instances of this class are created by GinoEngine.acquire().

    +
    +

    Note

    +

    GinoConnection may refer to zero or one underlying database +connection - when a GinoConnection is acquired with +lazy=True, the underlying connection may still be in the pool, +until a query API is called or get_raw_connection() is called.

    +

    Oppositely, one underlying database connection can be shared by many +GinoConnection instances when they are acquired with +reuse=True. The actual database connection is only returned to the +pool when the root GinoConnection is released. Read more +in GinoEngine.acquire() method.

    +
    +
    +

    See also

    +

    Engine and Connection

    +
    +
    +
    +async all(clause, *multiparams, **params)
    +

    Runs the given query in database, returns all results as a list.

    +

    This method accepts the same parameters taken by SQLAlchemy +execute(). You can pass in a raw +SQL string, or any SQLAlchemy query clauses.

    +

    If the given query clause is built by CRUD models, then the returning +rows will be turned into relevant model objects (Only one type of model +per query is supported for now, no relationship support yet). See +execution_options() for more information.

    +

    If the given parameters are parsed as “executemany” - bulk inserting +multiple rows in one call for example, the returning result from +database will be discarded and this method will return None.

    +
    + +
    +
    +property dialect
    +

    The Dialect in use, inherited +from the engine created this connection.

    +
    + +
    +
    +execution_options(**opt)
    +

    Set non-SQL options for the connection which take effect during +execution.

    +

    This method returns a copy of this GinoConnection which +references the same underlying database connection, but with the given +execution options set on the copy. Therefore, it is a good practice to +discard the copy immediately after use, for example:

    +
    row = await conn.execution_options(model=None).first(User.query)
    +
    +
    +

    This is very much the same as SQLAlchemy +execution_options(), it +actually does pass the execution options to the underlying SQLAlchemy +Connection. Furthermore, GINO added a +few execution options:

    +
    +
    Parameters
    +
      +
    • return_model – Boolean to control whether the returning results +should be loaded into model instances, where the model class is +defined in another execution option model. Default is True.

    • +
    • model – Specifies the type of model instance to create on return. +This has no effect if return_model is set to False. Usually +in queries built by CRUD models, this execution option is +automatically set. For now, GINO only supports loading each row into +one type of model object, relationships are not supported. Please use +multiple queries for that. None for no postprocessing (default).

    • +
    • timeout – Seconds to wait for the query to finish. None for +no time out (default).

    • +
    • loader

      A loader expression to load the database rows into +specified objective structure. It can be either:

      +
        +
      • A model class, so that the query will yield model instances of this +class. It is your responsibility to make sure all the columns of +this model is selected in the query.

      • +
      • A Column instance, so that each result +will be only a single value of this column. Please note, if you +want to achieve fetching the very first value, you should use +first() instead of +scalar(). However, using directly +scalar() is a more direct way.

      • +
      • A tuple nesting more loader expressions recursively.

      • +
      • A callable() function that will be called for each row to +fully customize the result. Two positional arguments will be passed +to the function: the first is the row instance, the second is a context +object which is only present if nested else None.

      • +
      • A Loader instance directly.

      • +
      • Anything else will be treated as literal values thus returned as +whatever they are.

      • +
      +

    • +
    +
    +
    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +async get_raw_connection(*, timeout=None)
    +

    Get the underlying database connection, acquire one if none present.

    +
    +
    Parameters
    +

    timeout – Seconds to wait for the underlying acquiring

    +
    +
    Returns
    +

    Underlying database connection instance depending on the +dialect in use

    +
    +
    Raises
    +

    TimeoutError if the acquiring timed out

    +
    +
    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    Cursors must work within transactions:

    +
    async with conn.transaction():
    +    async for user in conn.iterate(User.query):
    +        # handle each user without loading all users into memory
    +
    +
    +

    Alternatively, you can manually control how the cursor works:

    +
    async with conn.transaction():
    +    cursor = await conn.iterate(User.query)
    +    user = await cursor.next()
    +    users = await cursor.many(10)
    +
    +
    +

    Read more about how Cursor works.

    +

    Similarly, this method takes the same parameters as all().

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs the given query in database, returns exactly one result.

    +

    If the query returns no result, this method will raise +NoResultFound. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs the given query in database, returns at most one result.

    +

    If the query returns no result, this method will return None. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async prepare(clause)
    +
    + +
    +
    +property raw_connection
    +

    The current underlying database connection instance, type depends on +the dialect in use. May be None if self is a lazy connection.

    +
    + +
    +
    +async release(*, permanent=True)
    +

    Returns the underlying database connection to its pool.

    +

    If permanent=False, this connection will be set in lazy mode with +underlying database connection returned, the next query on this +connection will cause a new database connection acquired. This is +useful when this connection may still be useful again later, while some +long-running I/O operations are about to take place, which should not +take up one database connection or even transaction for that long time.

    +

    Otherwise with permanent=True (default), this connection will be +marked as closed after returning to pool, and be no longer usable +again.

    +

    If this connection is a reusing connection, then only this connection +is closed (depending on permanent), the reused underlying +connection will not be returned back to the pool.

    +

    Practically it is recommended to return connections in the reversed +order as they are borrowed, but if this connection is a reused +connection with still other opening connections reusing it, then on +release the underlying connection will be returned to the pool, +with all the reusing connections losing an available underlying +connection. The availability of further operations on those reusing +connections depends on the given permanent value.

    +
    +

    See also

    +

    GinoEngine.acquire()

    +
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +schema_for_object = <sqlalchemy.sql.schema._SchemaTranslateMap object>
    +

    A SQLAlchemy compatibility attribute, don’t use it for now, it bites.

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the query status.

    +

    The returning query status depends on underlying database and the +dialect in use. For asyncpg it is a string, you can parse it like this: +https://git.io/v7oze

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    Starts a database transaction.

    +

    There are two ways using this method: managed as an asynchronous +context manager:

    +
    async with conn.transaction() as tx:
    +    # run query in transaction
    +
    +
    +

    or manually awaited:

    +
    tx = await conn.transaction()
    +try:
    +    # run query in transaction
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    Where the tx is an instance of the +GinoTransaction class, feel free to read +more about it.

    +

    In the first managed mode, the transaction is automatically committed +on exiting the context block, or rolled back if an exception was raised +which led to the exit of the context. In the second manual mode, you’ll +need to manually call the +commit() or +rollback() methods on need.

    +

    If this is a lazy connection, entering a transaction will cause a new +database connection acquired if none was present.

    +

    Transactions may support nesting depending on the dialect in use. For +example in asyncpg, starting a second transaction on the same +connection will create a save point in the database.

    +

    For now, the parameters are directly passed to underlying database +driver, read asyncpg.connection.Connection.transaction() for +asyncpg.

    +
    + +
    + +
    +
    +class gino.engine.GinoEngine(dialect, pool, loop, logging_name=None, echo=None, execution_options=None)
    +

    Bases: object

    +

    Connects a Pool and +Dialect together to provide a source +of database connectivity and behavior.

    +

    A GinoEngine object is instantiated publicly using the +gino.create_engine() function or +db.set_bind() method.

    +
    +

    See also

    +

    Engine and Connection

    +
    +
    +
    +acquire(*, timeout=None, reuse=False, lazy=False, reusable=True)
    +

    Acquire a connection from the pool.

    +

    There are two ways using this method - as an asynchronous context +manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    which will guarantee the connection is returned to the pool when +leaving the async with block; or as a coroutine:

    +
    conn = await engine.acquire()
    +try:
    +    # play with the connection
    +finally:
    +    await conn.release()
    +
    +
    +

    where the connection should be manually returned to the pool with +conn.release().

    +

    Within the same context (usually the same Task, see +also Transaction), a nesting acquire by default re

    +
    +
    Parameters
    +
      +
    • timeout – Block up to timeout seconds until there is one free +connection in the pool. Default is None - block forever until +succeeded. This has no effect when lazy=True, and depends on the +actual situation when reuse=True.

    • +
    • reuse – Reuse the latest reusable acquired connection (before +it’s returned to the pool) in current context if there is one, or +borrow a new one if none present. Default is False for always +borrow a new one. This is useful when you are in a nested method call +series, wishing to use the same connection without passing it around +as parameters. See also: Transaction. A reusing +connection is not reusable even if reusable=True. If the reused +connection happened to be a lazy one, then the reusing connection is +lazy too.

    • +
    • lazy – Don’t acquire the actual underlying connection yet - do it +only when needed. Default is False for always do it immediately. +This is useful before entering a code block which may or may not make +use of a given connection object. Feeding in a lazy connection will +save the borrow-return job if the connection is never used. If +setting reuse=True at the same time, then the reused connection - +if any - applies the same laziness. For example, reusing a lazy +connection with lazy=False will cause the reused connection to +acquire an underlying connection immediately.

    • +
    • reusable – Mark this connection as reusable or otherwise. This +has no effect if it is a reusing connection. All reusable connections +are placed in a stack, any reusing acquire operation will always +reuse the top (latest) reusable connection. One reusable connection +may be reused by several reusing connections - they all share one +same underlying connection. Acquiring a connection with +reusable=False and reusing=False makes it a cleanly isolated +connection which is only referenced once here.

    • +
    +
    +
    Returns
    +

    A GinoConnection object.

    +
    +
    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    Acquires a connection with reuse=True and runs +all() on it. reuse=True means you can safely +do this without borrowing more than one underlying connection:

    +
    async with engine.acquire():
    +    await engine.all('SELECT ...')
    +
    +
    +

    The same applies for other query methods.

    +
    + +
    +
    +async close()
    +

    Close the engine, by closing the underlying pool.

    +
    + +
    +
    +compile(clause, *multiparams, **params)
    +

    A shortcut for compile() on +the dialect, returns raw SQL string and parameters according to the +rules of the dialect.

    +
    + +
    +
    +connection_cls
    +

    Customizes the connection class to use, default is +GinoConnection.

    +

    alias of GinoConnection

    +
    + +
    +
    +property current_connection
    +

    Gets the most recently acquired reusable connection in the context. +None if there is no such connection.

    +
    +
    Returns
    +

    GinoConnection

    +
    +
    +
    + +
    +
    +property dialect
    +

    Read-only property for the +Dialect of this engine.

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs first(), See all().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    This requires that there is a reusable connection in the current +context, and an active transaction is present. Then its +GinoConnection.iterate() is executed and returned.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs one(), See all().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs one_or_none(), See all().

    +
    + +
    +
    +property raw_pool
    +

    Read-only access to the underlying database connection pool instance. +This depends on the actual dialect in use, Pool +of asyncpg for example.

    +
    + +
    +
    +repr(color=False)
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs scalar(), See all().

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs status(). See also all().

    +
    + +
    +
    +transaction(*args, timeout=None, reuse=True, reusable=True, **kwargs)
    +

    Borrows a new connection and starts a transaction with it.

    +

    Different to GinoConnection.transaction(), transaction on engine +level supports only managed usage:

    +
    async with engine.transaction() as tx:
    +    # play with transaction here
    +
    +
    +

    Where the implicitly acquired connection is available as +tx.connection.

    +

    By default, transaction() acquires connection with +reuse=True and reusable=True, that means it by default tries to +create a nested transaction instead of a new transaction on a new +connection. You can change the default behavior by setting these two +arguments.

    +

    The other arguments are the same as +transaction() on connection.

    + +
    +
    Returns
    +

    A asynchronous context manager that yields a +GinoTransaction

    +
    +
    +
    + +
    +
    +update_execution_options(**opt)
    +

    Update the default execution_options dictionary +of this GinoEngine.

    + +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.exceptions.html b/docs/en/1.0/reference/api/gino.exceptions.html new file mode 100644 index 0000000..ee6b333 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.exceptions.html @@ -0,0 +1,241 @@ + + + + + + + + gino.exceptions module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.exceptions module

    +
    +
    +exception gino.exceptions.GinoException
    +

    Bases: Exception

    +
    + +
    +
    +exception gino.exceptions.MultipleResultsFound
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoResultFound
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoSuchRowError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UninitializedError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UnknownJSONPropertyError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.ext.html b/docs/en/1.0/reference/api/gino.ext.html new file mode 100644 index 0000000..b3d560b --- /dev/null +++ b/docs/en/1.0/reference/api/gino.ext.html @@ -0,0 +1,224 @@ + + + + + + + + gino.ext package - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.ext package

    +
    +

    Module contents

    +

    Namespace package for GINO extensions.

    +

    This namespace package didn’t use any of the 3 official solutions. Instead, +gino.ext adds a hook into sys.meta_path, and utilize the Entry points +to find the extensions.

    +

    Any GINO extension package should provide an entry point like this:

    +
    [gino.extensions]
    +starlette = gino_starlette
    +
    +
    +

    So that the Python package gino_starlette will also be importable through +gino.ext.starlette.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.html b/docs/en/1.0/reference/api/gino.html new file mode 100644 index 0000000..f463d46 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.html @@ -0,0 +1,262 @@ + + + + + + + + gino package - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino package

    + + +
    +

    Module contents

    +
    +
    +gino.create_engine(*args, **kwargs)
    +

    Shortcut for sqlalchemy.create_engine() with strategy="gino".

    +
    + +
    +
    +gino.get_version()
    +

    Get current GINO version.

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.json_support.html b/docs/en/1.0/reference/api/gino.json_support.html new file mode 100644 index 0000000..1008c74 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.json_support.html @@ -0,0 +1,347 @@ + + + + + + + + gino.json_support module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.json_support module

    +
    +
    +class gino.json_support.ArrayProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.BooleanProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.DateTimeProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.IntegerProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.JSONProperty(default=None, prop_name='profile')
    +

    Bases: object

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +get_profile(instance)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    +
    +reload(instance)
    +
    + +
    +
    +save(instance, value=<object object>)
    +
    + +
    + +
    +
    +class gino.json_support.ObjectProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.StringProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.loader.html b/docs/en/1.0/reference/api/gino.loader.html new file mode 100644 index 0000000..546740d --- /dev/null +++ b/docs/en/1.0/reference/api/gino.loader.html @@ -0,0 +1,630 @@ + + + + + + + + gino.loader module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.loader module

    +
    +
    +class gino.loader.AliasLoader(alias, *columns, **extras)
    +

    Bases: gino.loader.ModelLoader

    +

    The same as ModelLoader, kept for compatibility.

    +
    + +
    +
    +class gino.loader.CallableLoader(func)
    +

    Bases: gino.loader.Loader

    +

    Load the row by calling a specified function.

    +
    +
    Parameters
    +

    func – A callable() taking 2 positional arguments (row, context) +that will be called in do_load(), returning the loaded result.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to the given function.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    The result calling the given function, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ColumnLoader(column)
    +

    Bases: gino.loader.Loader

    +

    Load a given column in the row.

    +
    +
    Parameters
    +

    column – The column name as str, or a +Column instance to avoid name conflict.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – Not used.

    • +
    +
    +
    Returns
    +

    The value of the specified column, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.Loader
    +

    Bases: object

    +

    The abstract base class of loaders.

    +

    Loaders are used to load raw database rows into expected results. +GinoEngine will use the loader set on the loader value of +the execution_options(), for example:

    +
    from sqlalchemy import text, create_engine
    +from gino.loader import ColumnLoader
    +
    +e = await create_engine("postgresql://localhost/gino", strategy="gino")
    +q = text("SELECT now() as ts")
    +loader = ColumnLoader("ts")
    +ts = await e.first(q.execution_options(loader=loader))  # datetime
    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    Must be implemented in subclasses.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    Any result that the loader is supposed to return, followed by a boolean +value indicating if the result is distinct.

    +
    +
    +
    + +
    +
    +classmethod get(value)
    +

    Automatically create a loader based on the type of the given value.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    value type

    loader type

    tuple

    TupleLoader

    callable()

    CallableLoader

    Column, +Label

    ColumnLoader

    Model

    ModelLoader

    Alias

    AliasLoader

    Loader

    as is

    any other types

    ValueLoader

    +
    +
    Parameters
    +

    value – Any supported value above.

    +
    +
    Returns
    +

    A loader instance.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +property query
    +

    Generate a query from this loader.

    +

    This is an experimental feature, not all loaders support this.

    +
    +
    Returns
    +

    A query instance with the loader execution option set to self.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ModelLoader(model, *columns, **extras)
    +

    Bases: gino.loader.Loader

    +

    A loader that loads a row into a GINO model instance.

    +

    This loader generates an instance of the given model type and fills the instance +with attributes according to the other given parameters:

    +
      +
    • Load each column attribute listed in the given columns positional arguments.

    • +
    • If columns is not given, all defined columns of the model will be loaded.

    • +
    • For each keyword argument, its value will be used to generate a loader using +Loader.get(), and the loaded value will be setattr() to the model +instance under the name of the key.

    • +
    +

    For example, the simplest select and load:

    +
    sqlalchemy.select([User]).execution_options(loader=ModelLoader(User))
    +
    +
    +

    Select only the name column, and still load it into model instances:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, User.name)
    +)
    +
    +
    +

    This would also yield User instances, with all column attributes as None but +name.

    +

    Nest a ValueLoader:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, id=1)
    +)
    +
    +
    +

    1 is then converted into a ValueLoader and mocked the id attribute +of all returned User instances as 1.

    +

    Nest another ModelLoader:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company)
    +)
    +
    +
    +

    Likewise, Company is converted into a ModelLoader to load the +Company columns from the joined result, and the Company instances are set to +the company attribute of each User instance using setattr().

    +
    +
    Parameters
    +
      +
    • model – A subclass of Model to instantiate.

    • +
    • columns – A list of Column or str to +load, default is all the columns in the model.

    • +
    • extras – Additional attributes to load on the model instance.

    • +
    +
    +
    +
    +
    +distinct(*columns)
    +

    Configure this loader to reuse instances that have the same values of all the +give columns.

    +
    +
    Parameters
    +

    columns – Preferably Column instances.

    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    The model instance, followed by a boolean value indicating if the +result is distinct.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +load(*columns, **extras)
    +

    Update the loader with new rules.

    +

    After initialization, the rules of this loader can still be updated. This is +useful when using the model class as a shortcut of ModelLoader where +possible, chaining with a load() to initialize the rules, for example:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company.load('name'))
    +)
    +
    +
    +
    +
    Parameters
    +
      +
    • columns – If provided, replace the columns to load with the given ones.

    • +
    • extras – Update the loader with new extras.

    • +
    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +none_as_none(enabled=True)
    +

    Deprecated method for compatibility, does nothing.

    +
    + +
    +
    +on(on_clause)
    +

    Specify the on_clause for generating joined queries.

    +

    This is an experimental feature, used by get_from().

    +
    +
    Parameters
    +

    on_clause – An expression to feed into +join().

    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.TupleLoader(values)
    +

    Bases: gino.loader.Loader

    +

    Load multiple values into a tuple.

    +
    +
    Parameters
    +

    values – A tuple, each item is converted into a loader with +Loader.get().

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to sub-loaders.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    A tuple with loaded results from all sub-loaders, followed by +True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ValueLoader(value)
    +

    Bases: gino.loader.Loader

    +

    A loader that always return the specified value.

    +
    +
    Parameters
    +

    value – The value to return on load.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – Not used.

    • +
    • context – Not used.

    • +
    +
    +
    Returns
    +

    The given value, followed by True.

    +
    +
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.schema.html b/docs/en/1.0/reference/api/gino.schema.html new file mode 100644 index 0000000..04f0159 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.schema.html @@ -0,0 +1,325 @@ + + + + + + + + gino.schema module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.schema module

    +
    +
    +class gino.schema.AsyncSchemaDropper(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    Bases: gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaDropper

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, drop_ok=False)
    +
    + +
    +
    +async visit_table(table, drop_ok=False, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaGenerator(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    Bases: gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaGenerator

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, create_ok=False)
    +
    + +
    +
    +async visit_table(table, create_ok=False, include_foreign_key_constraints=None, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaTypeMixin
    +

    Bases: object

    +
    +
    +async create_async(bind=None, checkfirst=False)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncVisitor
    +

    Bases: object

    +
    +
    +async traverse_single(obj, **kw)
    +
    + +
    + +
    +
    +class gino.schema.GinoSchemaVisitor(item)
    +

    Bases: object

    +
    +
    +async create(bind=None, *args, **kwargs)
    +
    + +
    +
    +async create_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    +
    +async drop(bind=None, *args, **kwargs)
    +
    + +
    +
    +async drop_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    + +
    +
    +gino.schema.patch_schema(db)
    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.strategies.html b/docs/en/1.0/reference/api/gino.strategies.html new file mode 100644 index 0000000..ecdae0a --- /dev/null +++ b/docs/en/1.0/reference/api/gino.strategies.html @@ -0,0 +1,233 @@ + + + + + + + + gino.strategies module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.strategies module

    +
    +
    +class gino.strategies.GinoStrategy
    +

    Bases: sqlalchemy.engine.strategies.EngineStrategy

    +

    A SQLAlchemy engine strategy for GINO.

    +

    This strategy is initialized automatically as gino is imported.

    +

    If sqlalchemy.create_engine() uses strategy="gino", it will return a +Coroutine, and treat URL prefix postgresql:// or +postgres:// as postgresql+asyncpg://.

    +
    +
    +async create(name_or_url, loop=None, **kwargs)
    +

    Given arguments, returns a new Engine instance.

    +
    + +
    +
    +engine_cls
    +

    alias of gino.engine.GinoEngine

    +
    + +
    +
    +name = 'gino'
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/api/gino.transaction.html b/docs/en/1.0/reference/api/gino.transaction.html new file mode 100644 index 0000000..12b04a8 --- /dev/null +++ b/docs/en/1.0/reference/api/gino.transaction.html @@ -0,0 +1,329 @@ + + + + + + + + gino.transaction module - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.transaction module

    +
    +
    +class gino.transaction.GinoTransaction(conn, args, kwargs)
    +

    Bases: object

    +

    Represents an underlying database transaction and its connection, offering +methods to manage this transaction.

    +

    GinoTransaction is supposed to be created by either +gino.engine.GinoConnection.transaction(), or +gino.engine.GinoEngine.transaction(), or +gino.api.Gino.transaction(), shown as follows:

    +
    async with db.transaction() as tx:
    +    ...
    +
    +async with engine.transaction() as tx:
    +    ...
    +
    +async with conn.transaction() as tx:
    +    ...
    +
    +tx = await conn.transaction()
    +try:
    +    ...
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    When in use with asynchronous context manager, GinoTransaction +will be in managed mode, while the last example with await will put +the GinoTransaction in manual mode where you have to call +the commit() or rollback() to manually close the transaction.

    +

    In managed mode the transaction will be automatically committed or +rolled back on exiting the async with block depending on whether there +is an exception or not. Meanwhile, you can explicitly exit the transaction +early by raise_commit() or raise_rollback() which will raise +an internal exception managed by the asynchronous context manager and +interpreted as a commit or rollback action. In a nested transaction +situation, the two exit-early methods always close up the very transaction +which the two methods are referenced upon - all children transactions are +either committed or rolled back correspondingly, while no parent +transaction was ever touched. For example:

    +
    async with db.transaction() as tx1:
    +    async with db.transaction() as tx2:
    +        async with db.transaction() as tx3:
    +            tx2.raise_rollback()
    +            # Won't reach here
    +        # Won't reach here
    +    # Continues here with tx1, with both tx2 and tx3 rolled back.
    +
    +    # For PostgreSQL, tx1 can still be committed successfully because
    +    # tx2 and tx3 are just SAVEPOINTs in transaction tx1
    +
    +
    +
    +

    Tip

    +

    The internal exception raised from raise_commit() and +raise_rollback() is a subclass of BaseException, so +normal try ... except Exception: can’t trap the commit or rollback.

    +
    +
    +
    +async commit()
    +

    Only available in manual mode: manually commit this transaction.

    +
    + +
    +
    +property connection
    +

    Accesses to the GinoConnection of this +transaction. This is useful if when the transaction is started from +db or engine where the connection is implicitly acquired for +you together with the transaction.

    +
    + +
    +
    +raise_commit()
    +

    Only available in managed mode: skip rest of the code in this +transaction and commit immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    async with db.transaction() as tx:
    +    await user.update(age=64).apply()
    +    tx.raise_commit()
    +    await user.update(age=32).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +raise_rollback()
    +

    Only available in managed mode: skip rest of the code in this +transaction and rollback immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    assert user.age == 64  # assumption
    +
    +async with db.transaction() as tx:
    +    await user.update(age=32).apply()
    +    tx.raise_rollback()
    +    await user.update(age=128).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +property raw_transaction
    +

    Accesses to the underlying transaction object, whose type depends on +the dialect in use.

    +
    + +
    +
    +async rollback()
    +

    Only available in manual mode: manually rollback this transaction.

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/extensions.html b/docs/en/1.0/reference/extensions.html new file mode 100644 index 0000000..4859b34 --- /dev/null +++ b/docs/en/1.0/reference/extensions.html @@ -0,0 +1,217 @@ + + + + + + + + Extensions - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/extensions/sanic.html b/docs/en/1.0/reference/extensions/sanic.html new file mode 100644 index 0000000..e9e3a98 --- /dev/null +++ b/docs/en/1.0/reference/extensions/sanic.html @@ -0,0 +1,326 @@ + + + + + + + + Sanic Support - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/extensions/starlette.html b/docs/en/1.0/reference/extensions/starlette.html new file mode 100644 index 0000000..4e91e68 --- /dev/null +++ b/docs/en/1.0/reference/extensions/starlette.html @@ -0,0 +1,323 @@ + + + + + + + + Starlette Support - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    Note

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/extensions/tornado.html b/docs/en/1.0/reference/extensions/tornado.html new file mode 100644 index 0000000..a384a81 --- /dev/null +++ b/docs/en/1.0/reference/extensions/tornado.html @@ -0,0 +1,205 @@ + + + + + + + + Tornado Support - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/reference/history.html b/docs/en/1.0/reference/history.html new file mode 100644 index 0000000..b14167d --- /dev/null +++ b/docs/en/1.0/reference/history.html @@ -0,0 +1,900 @@ + + + + + + + + History - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    History

    +
    +

    GINO 1.0

    +
    +

    Migrating to GINO 1.0

    +

    GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you’re using one of them, you should install GINO 1.0 with extras:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +

    Extension Module

    Installation in GINO 1.0

    gino.ext.starlette

    pip install gino[starlette]

    gino.ext.aiohttp

    pip install gino[aiohttp]

    gino.ext.sanic

    pip install gino[sanic]

    gino.ext.tornado

    pip install gino[tornado]

    gino.ext.quart

    pip install gino[quart]

    +

    The new extension packages are backward-compatible, so there’s no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed gino[starlette]:

    +
    from gino.ext.starlette import Gino
    +
    +
    +

    GINO 1.0 switched to Poetry for package and +dependency management and started to use the +src layout. This shouldn’t +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process:

    +
      +
    • Source files are now located under src directory.

    • +
    • The src dist on PyPI does not include tests, docs and some other files due to +a limitation of Poetry.

    • +
    +
    +
    +

    1.0.1 (2020-06-08)

    +
      +
    • Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4)

    • +
    • Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672)

    • +
    • Multiple JSON property fixes (#661 #662 #695)

    • +
    • Fixed extension typing issue (#673 #674)

    • +
    • Fixed model override behavior (#694)

    • +
    • Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696)

    • +
    +
    +
    +

    1.0.0 (2020-04-26)

    +
      +
    • Switched to Poetry for package and dependency management.

    • +
    • [Breaking] Moved built-in extension modules to separate PyPI packages.

    • +
    • Switched to src layout.

    • +
    • Switched to black code style.

    • +
    • Better documentation.

    • +
    • [Breaking] none_as_none() is now always enabled.

    • +
    • Added representation method for engine.

    • +
    • Protected the URL instance fed to set_bind() from manipulation.

    • +
    • Replaced some assert with AssertionError (#258 #655)

    • +
    +
    +
    +
    +

    GINO 0.8

    +

    This is also version 1.0 release candidate.

    +
    +

    Migrating to GINO 0.8

    +
    +

    1. contextvars

    +

    We introduced aiocontextvars 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the enable_inherit() or +disable_inherit() calls, because they are the default behavior now thus +no longer exist. However, you’ll need to confirm that the event loop in use is +always created after importing gino or aiocontextvars, or the patch +won’t work correctly.

    +

    There is nothing to worry about in Python 3.7.

    +
    +
    +

    2. none_as_none

    +

    When GINO tries to load a row with all NULL values into an instance, it +will now by default return None instead of an instance with all None +attributes. To recover the default behavior of 0.7, please specify +none_as_none(False) in affected model loader.

    +

    This is especially applicable to relationship sub-loaders - if the sub-loader +found it all NULL, no instance will be set to parent instance. For +example:

    +
    child = await Child.load(parent=Parent).query.gino.first()
    +
    +
    +

    If child.parent_id is NULL in database, then the child instance +won’t be called with any setattr(child, 'parent', ...) at all. (If you need +child.parent == None in this case, consider setting default value +parent = None in child model.)

    +

    Please note, it is deprecated to disable none_as_none, and disabling will +be removed in GINO 1.0.

    +
    +
    +
    +

    0.8.7 (2020-04-19)

    +
      +
    • Improved error handling when attribute names collide (Contributed by Reskov in #637 #638)

    • +
    • Fixed with_bind usability in aiohttp extension (#518)

    • +
    +
    +
    +

    0.8.6 (2020-02-10)

    +
      +
    • Fixed JSONPathType bind processor for asyncpg (#609)

    • +
    • Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600)

    • +
    • Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629)

    • +
    • Added distinct() to Alias (#628)

    • +
    +
    +
    +

    0.8.5 (2019-11-19)

    +
      +
    • Improved support for __tablename__ in declared_attr (Contributed by Roald Storm in #592)

    • +
    +
    +
    +

    0.8.4 (2019-11-09)

    +
      +
    • Better loader support for models in subqueries (#573 #585)

    • +
    • Allowed __tablename__ to be a declared_attr (#579 #582)

    • +
    • Fixed Sanic 19.9.0 compatibility (#569)

    • +
    • Added one() and one_or_none() (Contributed by Ilaï Deutel in #577)

    • +
    • Improved Starleet extension compatibility (Contributed by Jim O’Brien in #538)

    • +
    • Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533)

    • +
    • Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520)

    • +
    • Fixed Grammar (Contributed by Simeon J Morgan in #504)

    • +
    +
    +
    +

    0.8.3 (2019-06-06)

    +
      +
    • Fixed deprecated warnings in asyncpg dialect and aiohttp (#425)

    • +
    • Customizable db attribute name in aiohttp app instance (#457)

    • +
    • Added Starlette support (#486)

    • +
    +
    +
    +

    0.8.2 (2019-03-07)

    +
      +
    • Added exception for unknown JSON properties (#406 #408)

    • +
    • Supported Quart 0.7 (#411)

    • +
    • Accepted kwargs for db init in extensions (#407 #427)

    • +
    • Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440)

    • +
    • Added NullPool (#437 #441)

    • +
    • Unpinned dependency versions (#447)

    • +
    • Added support for SQLAlchemy 1.3 (#433 #451)

    • +
    +
    +
    +

    0.8.1 (2018-12-08)

    +
      +
    • Alias supported Label (#365)

    • +
    • Docs update (#308, 4c59ad, #401 by Pascal van Kooten)

    • +
    • Version requirement for SQLAlchemy is updated to >=1.2 (#378 #382)

    • +
    • Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) +* And all other extensions (#395)

    • +
    • Supported Tornado 5 (#396, also thanks to Vladimir Goncharov)

    • +
    • Fixed custom JSON/JSONB type support (#402 #403)

    • +
    +

    (Most fixes done by Tony Wang)

    +
    +
    +

    0.8.0 (2018-10-16)

    +
      +
    • Welcome Tony Wang to the maintenance team (#335)

    • +
    • Allowed custom column names (#261 #297)

    • +
    • Allowed column instance in model.load() (Contributed by Jekel in #323)

    • +
    • [Breaking] Upgraded to aiocontextvars 0.2.0 (#333)

    • +
    • Fixed bug that the same empty stack is shared between sub-tasks (#313 #334)

    • +
    • [Breaking] Made none_as_none() the default behavior (#351)

    • +
    • Bug fixes and docs update

    • +
    +
    +
    +
    +

    GINO 0.7

    +

    This is also version 1.0 beta 3.

    +
    +

    0.7.7 (2018-12-08)

    +
      +
    • Backported fix for custom JSON/JSONB type support (#402 #403)

    • +
    +
    +
    +

    0.7.6 (2018-08-26)

    +
      +
    • Updated library support (Contributed by Tony Wang in #275 #309)

    • +
    • Added none_as_none() (#281 #282)

    • +
    • Added ARRAY alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289)

    • +
    • Added Model.lookup() to prevent updating whole table without primary key (#287 #288)

    • +
    • Added DB_ECHO in extension options (Contributed by Mykyta Holubakha in #298)

    • +
    • Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305)

    • +
    • Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304)

    • +
    • Fixed to raise UninitializedError if bind is None (Contributed by Tony Wang in #307 #310)

    • +
    +
    +
    +

    0.7.5 (2018-07-26)

    +
      +
    • Added friendly error message when using abstract models by mistake (#224)

    • +
    • Supported Python 3.7 (Contributed by Tony Wang in #265)

    • +
    • Updated documentation

    • +
    • Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280)

    • +
    +
    +
    +

    0.7.4 (2018-06-10)

    +
      +
    • Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228)

    • +
    • Added Quart support (#213)

    • +
    • Fixed Tornado options parsing (#231)

    • +
    • Improved coding style and test coverage

    • +
    +
    +
    +

    0.7.3 (2018-05-19)

    +
      +
    • Fix for failing binary type (#225)

    • +
    +
    +
    +

    0.7.2 (2018-05-15)

    +
      +
    • Added prepared statement support (#14)

    • +
    • Added dsn in extension config (Contributed by Yurii Shtrikker in #215)

    • +
    +
    +
    +

    0.7.1 (2018-05-03)

    +
      +
    • Added support for inline model constraints (Contributed by Kinware in #198)

    • +
    • Added docs and tests for using SSL (#202)

    • +
    • Added declared_attr (#204)

    • +
    • Allowed ModelLoader passively load partial model (#216)

    • +
    +
    +
    +

    0.7.0 (2018-04-18)

    +
      +
    • Added Python 3.5 support (#187)

    • +
    • Added support to use dict as ident for Model.get (#192)

    • +
    • Added result loader (partial relationship support) (#13)

    • +
    • Added documentation on relationship and transaction (#146)

    • +
    +
    +
    +
    +

    GINO 0.6

    +

    This is also version 1.0 beta 2.

    +
    +

    Migrating to GINO 0.6

    +
    +

    1. Task Local

    +

    We created a new Python package aiocontextvars from previous local.py. If +you made use of the task local features, you should install this package.

    +

    Previous gino.enable_task_local() and gino.disable_task_local() are +replaced by aiocontextvars.enable_inherit() and +aiocontextvars.disable_inherit(). However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars by default offers task +local even without enable_inherit(), which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars is installed.

    +

    There is no gino.get_local() and gino.reset_local() relevant in +aiocontextvars. The similar thing is aiocontextvars.ContextVar instance +through its get(), set() and delete() methods.

    +

    Previous gino.is_local_root() is now +not aiocontextvars.Context.current().inherited.

    +
    +
    +

    2. Engine

    +

    GINO 0.6 hides asyncpg.Pool behind the new SQLAlchemy-alike +gino.GinoEngine. Instead of doing this in 0.5:

    +
    async with db.create_pool('postgresql://...') as pool:
    +    # your code here
    +
    +
    +

    You should change it to this in 0.6:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +

    This equals to:

    +
    engine = await gino.create_engine('postgresql://...')
    +db.bind = engine
    +try:
    +    # your code here
    +finally:
    +    db.bind = None
    +    await engine.close()
    +
    +
    +

    Or:

    +
    engine = await db.set_bind('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Or even this:

    +
    db = await gino.Gino('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Choose whichever suits you the best.

    +

    Obviously GinoEngine doesn’t provide asyncpg.Pool methods directly any +longer, but you can get the underlying asyncpg.Pool object through +engine.raw_pool property.

    +

    GinoPool.get_current_connection() is now changed to current_connection +property on GinoEngine instances to support multiple engines.

    +

    GinoPool.execution_option is gone, instead update_execution_options() +on GinoEngine instance is available.

    +

    GinoPool().metadata is gone, dialect is still available.

    +

    GinoPool.release() is removed in GinoEngine and Gino, the +release() method on GinoConnection object should be used instead.

    +

    These methods exist both in 0.5 GinoPool and 0.6 GinoEngine: +close(), acquire(), all(), first(), scalar(), status().

    +
    +
    +

    3. GinoConnection

    +

    Similarly, GinoConnection in 0.6 is no longer a subclass of +asyncpg.Connection, instead it has a asyncpg.Connection instance, +accessable through GinoConnection.raw_connection property.

    +

    GinoConnection.metadata is deleted in 0.6, while dialect remained.

    +

    GinoConnection.execution_options() is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior.

    +

    GinoConnection.release() is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +permanent=False to remain its previous behavior.

    +

    And all(), first(), scalar(), status(), iterate(), +transaction() remained in 0.6.

    +
    +
    +

    4. Query API

    +

    All five query APIs all(), first(), scalar(), status(), +iterate() now accept the same parameters as SQLAlchemy execute(), +meaning they accept raw SQL text, or multiple sets of parameters for +“executemany”. Please note, if the parameters are recognized as “executemany”, +none of the methods will return anything. Meanwhile, they no longer accept the +parameter bind if they did. Just use the API on the GinoEngine or +GinoConnection object instead.

    +
    +
    +

    5. Transaction

    +

    Transaction interface is rewritten. Now in 0.6, a GinoTransaction object is +provided consistently from all 3 methods:

    +
    async with db.transaction() as tx:
    +    # within transaction
    +
    +async with engine.transaction() as tx:
    +    # within transaction
    +
    +async with engine.acquire() as conn:
    +    async with conn.transaction() as tx:
    +        # within transaction
    +
    +
    +

    And different usage with await:

    +
    tx = await db.transaction()
    +try:
    +    # within transaction
    +    await tx.commit()
    +except:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    The GinoConnection object is available at tx.connection, while +underlying transaction object from database driver is available at +tx.transaction - for asyncpg it is an asyncpg.transaction.Transaction +object.

    +
    +
    +
    +

    0.6.6 (2018-05-18)

    +
      +
    • Backported a fix for failing binary type (#225)

    • +
    +
    +
    +

    0.6.5 (2018-04-18)

    +
      +
    • Abandoned 0.6.4 and keep 0.6.x stable

    • +
    • Backported doc for transaction

    • +
    +
    +
    +

    0.6.4 (2018-04-16)

    +

    Abandoned version, please use 0.7.0 instead.

    +
    +
    +

    0.6.3 (2018-04-08)

    +
      +
    • Added aiohttp support

    • +
    • Added support for calling create() on model instances (Contributed by Kinware in #178 #180)

    • +
    • Fixed get() by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184)

    • +
    +
    +
    +

    0.6.2 (2018-03-24)

    +
      +
    • Fixed SQLAlchemy prefetch issue (#141)

    • +
    • Fixed issue that mixin class on Model not working (#174)

    • +
    • Added more documentation (Thanks Olaf Conradi for reviewing)

    • +
    +
    +
    +

    0.6.1 (2018-03-18)

    +
      +
    • Fixed create and drop for Enum type (#160)

    • +
    • A bit more documentation (#159)

    • +
    +
    +
    +

    0.6.0 (2018-03-14)

    +
      +
    • [Breaking] API Refactored, Pool replaced with Engine

      +
        +
      • New API Engine replaced asyncpg Pool (#59)

      • +
      • Supported different dialects, theoretically

      • +
      • Used aiocontextvars instead of builtin task local (#89)

      • +
      +
    • +
    • [Breaking] Fixed query API with multiparams (executemany) to return correctly (#20)

    • +
    • [Breaking] The query methods no longer accept the parameter bind

    • +
    • [Breaking] Gino no longer exposes postgresql types

    • +
    • Added echo on engine (#142)

    • +
    • Added tests to cover 80% of code

    • +
    • Added gino extension on SchemaItem for create_all and so on (#76 #106)

    • +
    • Added gino extension on model classes for create() or drop()

    • +
    • Added _update_request_cls on CRUDModel (#147)

    • +
    • Rewrote the documentation (#146)

    • +
    +
    +
    +
    +

    GINO 0.5

    +

    This is also version 1.0 beta 1.

    +
    +

    0.5.8 (2018-02-14)

    +
      +
    • Preparing for 0.6.0 which will be a breaking release

    • +
    • Fixed wrong value of Enum in creation (Contributed by Sergey Kovalev in #126)

    • +
    +
    +
    +

    0.5.7 (2017-11-24)

    +

    This is an emergency fix for 0.5.6.

    +
      +
    • Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114)

    • +
    • Added Model.outerjoin

    • +
    +
    +
    +

    0.5.6 (2017-11-23)

    +
      +
    • Changed to use unnamed statement when possible (#80 #90)

    • +
    • Added more example (Contributed by Kentoseth in #109)

    • +
    • Added Model.join and made Model selectable (Contributed by Ádám Barancsuk in #112 #113)

    • +
    +
    +
    +

    0.5.5 (2017-10-18)

    +
      +
    • Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87)

    • +
    • Added ability to reset local storage (#84)

    • +
    • Fixed bug in JSON property update

    • +
    • Added update chaining feature

    • +
    +
    +
    +

    0.5.4 (2017-10-04)

    +
      +
    • Updated example (Contributed by Kinware in #75)

    • +
    • Added Model.insert (Contributed by Neal Wang in #63)

    • +
    • Fixed issue that non-lazy acquiring fails dirty (#79)

    • +
    +
    +
    +

    0.5.3 (2017-09-23)

    +
      +
    • Fixed no module named cutils error (Contributed by Vladimir Goncharov in #73)

    • +
    +
    +
    +

    0.5.2 (2017-09-10)

    +
      +
    • Added missing driver name on dialect (#67)

    • +
    • Fixed dialect to support native decimal type (#67)

    • +
    +
    +
    +

    0.5.1 (2017-09-09)

    +

    This is an emergency fix for 0.5.0.

    +
      +
    • Reverted the extension, back to pure Python (#60)

    • +
    • Used SQLAlchemy RowProxy

    • +
    • Added first_or_404

    • +
    • Fixed bug that GinoPool cannot be inherited

    • +
    +
    +
    +

    0.5.0 (2017-09-03)

    +
      +
    • [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten

      +
        +
      • Extracted CRUD operations

      • +
      • Core operations are moved to dialect and execution context

      • +
      • Removed guess_model, switched to explicit execution options

      • +
      • Turned timeout parameter to an execution option

      • +
      • Extracted pool, connection and api from asyncpg_delegate

      • +
      +
    • +
    • Added support for SQLAlchemy execution options, and a few custom options

    • +
    • [Breaking] Made Model.select return rows by default (#39)

    • +
    • Moved get_or_404 to extensions (#38)

    • +
    • Added iterator on model classes (#43)

    • +
    • Added Tornado extension (Contributed by Vladimir Goncharov)

    • +
    • Added Model.to_dict (#47)

    • +
    • Added an extension module to update asyncpg.Record with processed results

    • +
    +
    +
    +
    +

    Early Development Releases

    +

    Considered as alpha releases.

    +
    +

    0.4.1 (2017-08-20)

    +
      +
    • Support select on model instance

    • +
    +
    +
    +

    0.4.0 (2017-08-15)

    +
      +
    • Made get_or_404 more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31)

    • +
    • Delegated sqlalchemy.__all__ (Contributed by Neal Wang in #10 #33)

    • +
    • [Breaking] Rewrote JSON/JSONB support (#29)

    • +
    • Added lazy parameter on db.acquire (Contributed by Binghan Li in #32)

    • +
    • Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34)

    • +
    • Fixed iterate API to be compatible with asyncpg (#32)

    • +
    • Unified exceptions

    • +
    • [Breaking] Changed update API (#29)

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.3.0 (2017-08-07)

    +
      +
    • Supported __table_args__ (#12)

    • +
    • Introduced task local to manage connection in context (#19)

    • +
    • Added query.gino extension for in-place execution

    • +
    • Refreshed README (#3)

    • +
    • Adopted PEP 487 (Contributed by Tony Wang in #17 #27)

    • +
    • Used weakref on __model__ of table and query (Contributed by Tony Wang)

    • +
    • Delegated asyncpg timeout parameter (Contributed by Neal Wang in #16 #22)

    • +
    +
    +
    +

    0.2.3 (2017-08-04)

    +
      +
    • Supported any primary key (Contributed by Tony Wang in #11)

    • +
    +
    +
    +

    0.2.2 (2017-08-02)

    +
      +
    • Supported SQLAlchemy result processor

    • +
    • Added rich support on JSON/JSONB

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.2.1 (2017-07-28)

    +
      +
    • Added update and delete API

    • +
    +
    +
    +

    0.2.0 (2017-07-28)

    +
      +
    • Changed API, no longer reuses asyncpg API

    • +
    +
    +
    +

    0.1.1 (2017-07-25)

    +
      +
    • Added db.bind

    • +
    • API changed: parameter conn renamed to optional bind

    • +
    • Delegated asyncpg Pool with db.create_pool

    • +
    • Internal enhancement and bug fixes

    • +
    +
    +
    +

    0.1.0 (2017-07-21)

    +
      +
    • First release on PyPI.

    • +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/search.html b/docs/en/1.0/search.html new file mode 100644 index 0000000..2500d6c --- /dev/null +++ b/docs/en/1.0/search.html @@ -0,0 +1 @@ +

    search.html

    \ No newline at end of file diff --git a/docs/en/1.0/searchindex.js b/docs/en/1.0/searchindex.js new file mode 100644 index 0000000..28e6197 --- /dev/null +++ b/docs/en/1.0/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["explanation","explanation/async","explanation/engine","explanation/why","how-to","how-to/alembic","how-to/contributing","how-to/crud","how-to/faq","how-to/json-props","how-to/loaders","how-to/pool","how-to/schema","how-to/transaction","index","reference","reference/api","reference/api/gino","reference/api/gino.aiocontextvars","reference/api/gino.api","reference/api/gino.crud","reference/api/gino.declarative","reference/api/gino.dialects","reference/api/gino.dialects.asyncpg","reference/api/gino.dialects.base","reference/api/gino.engine","reference/api/gino.exceptions","reference/api/gino.ext","reference/api/gino.json_support","reference/api/gino.loader","reference/api/gino.schema","reference/api/gino.strategies","reference/api/gino.transaction","reference/extensions","reference/extensions/sanic","reference/extensions/starlette","reference/extensions/tornado","reference/history","tutorials","tutorials/announcement","tutorials/fastapi","tutorials/tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["explanation.rst","explanation/async.rst","explanation/engine.rst","explanation/why.rst","how-to.rst","how-to/alembic.rst","how-to/contributing.rst","how-to/crud.rst","how-to/faq.rst","how-to/json-props.rst","how-to/loaders.rst","how-to/pool.rst","how-to/schema.rst","how-to/transaction.rst","index.rst","reference.rst","reference/api.rst","reference/api/gino.rst","reference/api/gino.aiocontextvars.rst","reference/api/gino.api.rst","reference/api/gino.crud.rst","reference/api/gino.declarative.rst","reference/api/gino.dialects.rst","reference/api/gino.dialects.asyncpg.rst","reference/api/gino.dialects.base.rst","reference/api/gino.engine.rst","reference/api/gino.exceptions.rst","reference/api/gino.ext.rst","reference/api/gino.json_support.rst","reference/api/gino.loader.rst","reference/api/gino.schema.rst","reference/api/gino.strategies.rst","reference/api/gino.transaction.rst","reference/extensions.rst","reference/extensions/sanic.rst","reference/extensions/starlette.rst","reference/extensions/tornado.rst","reference/history.rst","tutorials.rst","tutorials/announcement.rst","tutorials/fastapi.rst","tutorials/tutorial.rst"],objects:{"":{gino:[17,0,0,"-"]},"gino.aiocontextvars":{patch_asyncio:[18,1,1,""]},"gino.api":{Gino:[19,2,1,""],GinoExecutor:[19,2,1,""]},"gino.api.Gino":{Model:[19,3,1,""],acquire:[19,3,1,""],all:[19,3,1,""],bind:[19,3,1,""],compile:[19,3,1,""],first:[19,3,1,""],iterate:[19,3,1,""],model_base_classes:[19,4,1,""],no_delegate:[19,4,1,""],one:[19,3,1,""],one_or_none:[19,3,1,""],pop_bind:[19,3,1,""],query_executor:[19,4,1,""],scalar:[19,3,1,""],schema_visitor:[19,4,1,""],set_bind:[19,3,1,""],status:[19,3,1,""],transaction:[19,3,1,""],with_bind:[19,3,1,""]},"gino.api.GinoExecutor":{all:[19,3,1,""],first:[19,3,1,""],iterate:[19,3,1,""],load:[19,3,1,""],model:[19,3,1,""],one:[19,3,1,""],one_or_none:[19,3,1,""],query:[19,3,1,""],return_model:[19,3,1,""],scalar:[19,3,1,""],status:[19,3,1,""],timeout:[19,3,1,""]},"gino.crud":{Alias:[20,2,1,""],CRUDModel:[20,2,1,""],QueryModel:[20,2,1,""],UpdateRequest:[20,2,1,""]},"gino.crud.Alias":{distinct:[20,3,1,""],load:[20,3,1,""],on:[20,3,1,""]},"gino.crud.CRUDModel":{"delete":[20,4,1,""],alias:[20,3,1,""],append_where_primary_key:[20,3,1,""],create:[20,3,1,""],distinct:[20,3,1,""],get:[20,3,1,""],in_query:[20,3,1,""],load:[20,3,1,""],lookup:[20,3,1,""],none_as_none:[20,3,1,""],on:[20,3,1,""],query:[20,4,1,""],select:[20,3,1,""],to_dict:[20,3,1,""],update:[20,4,1,""]},"gino.crud.UpdateRequest":{apply:[20,3,1,""],update:[20,3,1,""]},"gino.declarative":{ColumnAttribute:[21,2,1,""],InvertDict:[21,2,1,""],Model:[21,2,1,""],declarative_base:[21,1,1,""],declared_attr:[21,1,1,""]},"gino.declarative.InvertDict":{invert_get:[21,3,1,""]},"gino.dialects":{asyncpg:[23,0,0,"-"],base:[24,0,0,"-"]},"gino.dialects.asyncpg":{AsyncEnum:[23,2,1,""],AsyncpgCompiler:[23,2,1,""],AsyncpgCursor:[23,2,1,""],AsyncpgDBAPI:[23,2,1,""],AsyncpgDialect:[23,2,1,""],AsyncpgExecutionContext:[23,2,1,""],AsyncpgIterator:[23,2,1,""],AsyncpgJSONPathType:[23,2,1,""],DBAPICursor:[23,2,1,""],GinoNullType:[23,2,1,""],NullPool:[23,2,1,""],Pool:[23,2,1,""],PreparedStatement:[23,2,1,""],Transaction:[23,2,1,""]},"gino.dialects.asyncpg.AsyncEnum":{create_async:[23,3,1,""],drop_async:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgCompiler":{bindtemplate:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgCursor":{forward:[23,3,1,""],many:[23,3,1,""],next:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgDBAPI":{Error:[23,4,1,""]},"gino.dialects.asyncpg.AsyncpgDialect":{colspecs:[23,4,1,""],cursor_cls:[23,4,1,""],dbapi_class:[23,4,1,""],driver:[23,4,1,""],execution_ctx_cls:[23,4,1,""],get_isolation_level:[23,3,1,""],has_schema:[23,3,1,""],has_sequence:[23,3,1,""],has_table:[23,3,1,""],has_type:[23,3,1,""],init_kwargs:[23,4,1,""],init_pool:[23,3,1,""],on_connect:[23,3,1,""],set_isolation_level:[23,3,1,""],statement_compiler:[23,4,1,""],supports_native_decimal:[23,4,1,""],transaction:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgJSONPathType":{bind_processor:[23,3,1,""]},"gino.dialects.asyncpg.DBAPICursor":{async_execute:[23,3,1,""],description:[23,3,1,""],get_statusmsg:[23,3,1,""],prepare:[23,3,1,""]},"gino.dialects.asyncpg.GinoNullType":{result_processor:[23,3,1,""]},"gino.dialects.asyncpg.NullPool":{acquire:[23,3,1,""],close:[23,3,1,""],raw_pool:[23,3,1,""],release:[23,3,1,""],repr:[23,3,1,""]},"gino.dialects.asyncpg.Pool":{acquire:[23,3,1,""],close:[23,3,1,""],raw_pool:[23,3,1,""],release:[23,3,1,""],repr:[23,3,1,""]},"gino.dialects.asyncpg.Transaction":{begin:[23,3,1,""],commit:[23,3,1,""],raw_transaction:[23,3,1,""],rollback:[23,3,1,""]},"gino.dialects.base":{AsyncDialectMixin:[24,2,1,""],BaseDBAPI:[24,2,1,""],Cursor:[24,2,1,""],DBAPICursor:[24,2,1,""],ExecutionContextOverride:[24,2,1,""],Pool:[24,2,1,""],PreparedStatement:[24,2,1,""],Transaction:[24,2,1,""]},"gino.dialects.base.AsyncDialectMixin":{compile:[24,3,1,""],cursor_cls:[24,4,1,""],dbapi:[24,3,1,""],dbapi_class:[24,4,1,""],init_pool:[24,3,1,""],transaction:[24,3,1,""]},"gino.dialects.base.BaseDBAPI":{Binary:[24,3,1,""],Error:[24,4,1,""],paramstyle:[24,4,1,""]},"gino.dialects.base.Cursor":{forward:[24,3,1,""],many:[24,3,1,""],next:[24,3,1,""]},"gino.dialects.base.DBAPICursor":{async_execute:[24,3,1,""],description:[24,3,1,""],execute:[24,3,1,""],executemany:[24,3,1,""],get_statusmsg:[24,3,1,""],prepare:[24,3,1,""]},"gino.dialects.base.ExecutionContextOverride":{get_result_proxy:[24,3,1,""],loader:[24,4,1,""],model:[24,4,1,""],process_rows:[24,3,1,""],return_model:[24,4,1,""],timeout:[24,4,1,""]},"gino.dialects.base.Pool":{acquire:[24,3,1,""],close:[24,3,1,""],raw_pool:[24,3,1,""],release:[24,3,1,""],repr:[24,3,1,""]},"gino.dialects.base.PreparedStatement":{all:[24,3,1,""],first:[24,3,1,""],iterate:[24,3,1,""],scalar:[24,3,1,""],status:[24,3,1,""]},"gino.dialects.base.Transaction":{begin:[24,3,1,""],commit:[24,3,1,""],raw_transaction:[24,3,1,""],rollback:[24,3,1,""]},"gino.engine":{GinoConnection:[25,2,1,""],GinoEngine:[25,2,1,""]},"gino.engine.GinoConnection":{all:[25,3,1,""],dialect:[25,3,1,""],execution_options:[25,3,1,""],first:[25,3,1,""],get_raw_connection:[25,3,1,""],iterate:[25,3,1,""],one:[25,3,1,""],one_or_none:[25,3,1,""],prepare:[25,3,1,""],raw_connection:[25,3,1,""],release:[25,3,1,""],scalar:[25,3,1,""],schema_for_object:[25,4,1,""],status:[25,3,1,""],transaction:[25,3,1,""]},"gino.engine.GinoEngine":{acquire:[25,3,1,""],all:[25,3,1,""],close:[25,3,1,""],compile:[25,3,1,""],connection_cls:[25,4,1,""],current_connection:[25,3,1,""],dialect:[25,3,1,""],first:[25,3,1,""],iterate:[25,3,1,""],one:[25,3,1,""],one_or_none:[25,3,1,""],raw_pool:[25,3,1,""],repr:[25,3,1,""],scalar:[25,3,1,""],status:[25,3,1,""],transaction:[25,3,1,""],update_execution_options:[25,3,1,""]},"gino.exceptions":{GinoException:[26,5,1,""],MultipleResultsFound:[26,5,1,""],NoResultFound:[26,5,1,""],NoSuchRowError:[26,5,1,""],UninitializedError:[26,5,1,""],UnknownJSONPropertyError:[26,5,1,""]},"gino.json_support":{ArrayProperty:[28,2,1,""],BooleanProperty:[28,2,1,""],DateTimeProperty:[28,2,1,""],IntegerProperty:[28,2,1,""],JSONProperty:[28,2,1,""],ObjectProperty:[28,2,1,""],StringProperty:[28,2,1,""]},"gino.json_support.ArrayProperty":{decode:[28,3,1,""],encode:[28,3,1,""]},"gino.json_support.BooleanProperty":{decode:[28,3,1,""],encode:[28,3,1,""],make_expression:[28,3,1,""]},"gino.json_support.DateTimeProperty":{decode:[28,3,1,""],encode:[28,3,1,""],make_expression:[28,3,1,""]},"gino.json_support.IntegerProperty":{decode:[28,3,1,""],encode:[28,3,1,""],make_expression:[28,3,1,""]},"gino.json_support.JSONProperty":{decode:[28,3,1,""],encode:[28,3,1,""],get_profile:[28,3,1,""],make_expression:[28,3,1,""],reload:[28,3,1,""],save:[28,3,1,""]},"gino.json_support.ObjectProperty":{decode:[28,3,1,""],encode:[28,3,1,""]},"gino.json_support.StringProperty":{make_expression:[28,3,1,""]},"gino.loader":{AliasLoader:[29,2,1,""],CallableLoader:[29,2,1,""],ColumnLoader:[29,2,1,""],Loader:[29,2,1,""],ModelLoader:[29,2,1,""],TupleLoader:[29,2,1,""],ValueLoader:[29,2,1,""]},"gino.loader.CallableLoader":{do_load:[29,3,1,""]},"gino.loader.ColumnLoader":{do_load:[29,3,1,""]},"gino.loader.Loader":{do_load:[29,3,1,""],get:[29,3,1,""],get_columns:[29,3,1,""],get_from:[29,3,1,""],query:[29,3,1,""]},"gino.loader.ModelLoader":{distinct:[29,3,1,""],do_load:[29,3,1,""],get_columns:[29,3,1,""],get_from:[29,3,1,""],load:[29,3,1,""],none_as_none:[29,3,1,""],on:[29,3,1,""]},"gino.loader.TupleLoader":{do_load:[29,3,1,""]},"gino.loader.ValueLoader":{do_load:[29,3,1,""]},"gino.schema":{AsyncSchemaDropper:[30,2,1,""],AsyncSchemaGenerator:[30,2,1,""],AsyncSchemaTypeMixin:[30,2,1,""],AsyncVisitor:[30,2,1,""],GinoSchemaVisitor:[30,2,1,""],patch_schema:[30,1,1,""]},"gino.schema.AsyncSchemaDropper":{visit_foreign_key_constraint:[30,3,1,""],visit_index:[30,3,1,""],visit_metadata:[30,3,1,""],visit_sequence:[30,3,1,""],visit_table:[30,3,1,""]},"gino.schema.AsyncSchemaGenerator":{visit_foreign_key_constraint:[30,3,1,""],visit_index:[30,3,1,""],visit_metadata:[30,3,1,""],visit_sequence:[30,3,1,""],visit_table:[30,3,1,""]},"gino.schema.AsyncSchemaTypeMixin":{create_async:[30,3,1,""],drop_async:[30,3,1,""]},"gino.schema.AsyncVisitor":{traverse_single:[30,3,1,""]},"gino.schema.GinoSchemaVisitor":{create:[30,3,1,""],create_all:[30,3,1,""],drop:[30,3,1,""],drop_all:[30,3,1,""]},"gino.strategies":{GinoStrategy:[31,2,1,""]},"gino.strategies.GinoStrategy":{create:[31,3,1,""],engine_cls:[31,4,1,""],name:[31,4,1,""]},"gino.transaction":{GinoTransaction:[32,2,1,""]},"gino.transaction.GinoTransaction":{commit:[32,3,1,""],connection:[32,3,1,""],raise_commit:[32,3,1,""],raise_rollback:[32,3,1,""],raw_transaction:[32,3,1,""],rollback:[32,3,1,""]},gino:{aiocontextvars:[18,0,0,"-"],api:[19,0,0,"-"],create_engine:[17,1,1,""],crud:[20,0,0,"-"],declarative:[21,0,0,"-"],dialects:[22,0,0,"-"],engine:[25,0,0,"-"],exceptions:[26,0,0,"-"],ext:[27,0,0,"-"],get_version:[17,1,1,""],json_support:[28,0,0,"-"],loader:[29,0,0,"-"],schema:[30,0,0,"-"],strategies:[31,0,0,"-"],transaction:[32,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","attribute","Python attribute"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:attribute","5":"py:exception"},terms:{"0x10a8ba860":12,"11th":3,"21s":40,"32c0feba61ea":40,"32c0feba61ea_add_users_t":40,"3apull_request":6,"4c59ad":37,"\u00e1d\u00e1m":[8,37],"\u4e00\u4e2a\u6c47\u7387":39,"\u4e00\u5b9a\u8981\u5168":39,"\u4e00\u65e6\u4ee3\u7801\u521d\u5177\u89c4\u6a21":39,"\u4e00\u679a":39,"\u4e00\u6837":39,"\u4e00\u79d2\u53ef\u8bfb\u767e\u4e07\u884c\u7684":39,"\u4e00\u7ad9\u5f0f\u5730\u89e3\u51b3\u4e86\u5e38\u7528":39,"\u4e09\u5e74\u540e\u7684\u4eca\u5929":39,"\u4e0a\u4e0b\u6587\u7ba1\u7406":39,"\u4e0a\u624b\u540e\u4f9d\u7136\u53ef\u4ee5\u5feb\u901f":39,"\u4e0a\u7ed9":39,"\u4e0a\u9762\u90a3\u4e2a":39,"\u4e0d\u4f1a\u53bb\u65e0\u7aef\u731c\u6d4b\u4e3b\u4eba\u7684\u610f\u56fe":39,"\u4e0d\u540c\u7684\u662f":39,"\u4e0d\u65ad\u6f14\u8fdb\u6210\u719f":39,"\u4e0d\u662f":39,"\u4e0d\u662f\u4e00\u4e2a\u4f20\u7edf\u7684":39,"\u4e0d\u6b62\u8fd9\u6837":39,"\u4e0e":39,"\u4e13\u4e1a\u7248\u5168\u5bb6\u6876":39,"\u4e13\u4e1a\u9886\u57df\u7684":39,"\u4e1a\u4f59\u65f6\u95f4\u9664\u4e86\u5f00\u53d1\u7ef4\u62a4":39,"\u4e2d\u8d1f\u8d23\u6784\u5efa":39,"\u4e3a":39,"\u4e3a\u4e86\u8ba9\u4e0d\u540c\u5e94\u7528\u573a\u666f\u4e0b\u7684\u7528\u6237\u4f53\u9a8c\u5230\u6700\u5927\u7684\u5584\u610f":39,"\u4e3a\u4e86\u9e21\u6bdb\u849c\u76ae\u7684\u5c0f\u4e8b\u5927\u52a8\u5e72\u6208":39,"\u4e3a\u4ec0\u4e48\u8fd9\u4e48\u773c\u719f":39,"\u4e3b\u8981\u8bf4\u7684\u662f\u5f00\u53d1\u6548\u7387":39,"\u4e4b\u7985\u5b8c\u7f8e\u8868\u8fbe\u4e86":39,"\u4e5f\u53ef\u4ee5\u7528":39,"\u4e5f\u63d0\u4f9b\u4e86\u5bf9\u8c61\u6620\u5c04\u7684\u5de5\u5177":39,"\u4e5f\u662f":39,"\u4e5f\u66fe\u5728\u521b\u4e1a\u7684\u6f6e\u6d41\u4e2d\u7559\u4e0b\u8eab\u5f71":39,"\u4e5f\u6709\u4e00\u5957\u7cbe\u5999\u7684\u663e\u5f0f\u673a\u5236":39,"\u4e86\u89e3\u4e00\u4e0b":39,"\u4e8c\u5341\u4e94\u516d\u5e74\u7684\u7f16\u7a0b\u53f2\u548c\u5341\u4e09\u56db\u5e74\u7684\u5de5\u4f5c\u7ecf\u9a8c\u6559\u4f1a\u4e86\u6211\u8bb8\u591a\u8f6f\u4ef6\u5f00\u53d1\u7684\u5965\u4e49":39,"\u4e92\u91d1":39,"\u4eb2\u8eab\u7ecf\u5386\u5e76\u89c1\u8bc1\u4e86\u8f6f\u4ef6\u6280\u672f\u968f\u7740\u624b\u6e38":39,"\u4ec0\u4e48\u7684\u90fd\u6709":39,"\u4ece\u4e0a\u624b\u6559\u7a0b\u5230\u539f\u7406\u8bf4\u660e\u5e94\u6709\u5c3d\u6709":39,"\u4ece\u6bd4\u8f83\u65e9\u5c31\u89e3\u8026\u4e86\u4e0d\u540c":39,"\u4ece\u7b80\u5355\u793a\u8303\u5230\u751f\u4ea7\u73af\u5883\u7684\u5404\u79cd\u4f8b\u5b50\u54c1\u79cd\u9f50\u5168":39,"\u4ee5\u4e0b\u662f\u8fd1\u6765\u7edf\u8ba1\u5230\u7684\u5173\u4e8e":39,"\u4ee5\u53ca":39,"\u4ee5\u53ca\u4e0b\u9762\u8fd9\u4e9b\u4e00\u76f4\u9700\u8981\u7684\u5e2e\u52a9":39,"\u4ee5\u53ca\u4e2d\u6587":39,"\u4ee5\u540c\u65f6\u83b7\u53d6\u6240\u6709\u7684\u4e66\u548c\u4ed6\u4eec\u7684\u4f5c\u8005":39,"\u4ee5\u8282\u7701\u5b66\u4e60\u548c\u8fc1\u79fb\u6210\u672c":39,"\u4f18\u52bf\u4e0e\u4e0d\u8db3":38,"\u4f20\u7edf":39,"\u4f46":39,"\u4f46\u4e0d\u662f\u4e00\u4e2a\u4f20\u7edf\u7684":39,"\u4f46\u5e94\u7528\u5230\u5927\u578b\u9879\u76ee\u4e2d\u5374\u5341\u5206\u8003\u9a8c\u5f00\u53d1\u4eba\u5458\u7684\u5e73\u5747\u6c34\u5e73":39,"\u4f46\u662f\u5bf9\u4e8e\u66f4\u590d\u6742\u7684\u67e5\u8be2":39,"\u4f46\u6ca1\u5f81\u5f97\u540c\u610f\u5c31\u4e0d\u8d34\u51fa\u6765\u4e86":39,"\u4f46\u90fd\u662f\u540c\u884c\u5c31\u4e0d\u591a\u8bc4\u4ef7\u4e86":39,"\u4f60\u4e00\u5b9a\u4f1a\u6709\u611f\u77e5\u7684":39,"\u4f60\u53ef\u4ee5\u7528":39,"\u4f60\u751a\u81f3\u53ef\u4ee5\u624b\u5199\u4efb\u4f55":39,"\u4f60\u80af\u5b9a\u8981\u95ee\u4e00\u53e5":39,"\u4f8b\u5982\u524d\u9762\u7684":39,"\u4fc4\u6587\u7684\u7ffb\u8bd1":39,"\u4fc4\u8bed\u6559\u7a0b":39,"\u4fee":39,"\u501f\u9274\u4e86":39,"\u505a\u529f\u80fd":39,"\u5143":39,"\u5148\u8bf4":38,"\u5168\u90fd\u517c\u5bb9":39,"\u5173\u4e8e\u4f5c\u8005":38,"\u5176\u4e2d":39,"\u518d\u52a0\u4e0a":39,"\u518d\u8bf4":38,"\u5199":39,"\u51fa\u54c1\u5fc5\u5c5e\u7cbe\u54c1\u7684":39,"\u51fa\u54c1\u65b9\u662f":39,"\u51fa\u95ee\u9898\u627e\u4e0d\u5230\u539f\u56e0":39,"\u5219\u4f1a\u5c06\u8fd9\u4e9b\u53d8\u66f4\u5e94\u7528\u5230\u6570\u636e\u5e93\u91cc":39,"\u5219\u662f":39,"\u521b\u9020\u51fa\u4e86\u4e00\u79cd\u7206\u70b8\u5f0f\u7684\u5316\u5b66\u53cd\u5e94":39,"\u52a0\u4e00\u9897\u661f\u661f":39,"\u52a0\u8f7d\u5668\u4e0e\u5173\u7cfb":39,"\u52a0\u8f7d\u5668\u7684\u7528\u6cd5\u4e5f\u662f\u5f88\u7b80\u5355\u7684":39,"\u5341\u5206\u5feb\u6377":39,"\u5343\u661f\u9879\u76ee":39,"\u5373\u5173\u7cfb\u5bf9\u8c61\u6620\u5c04":39,"\u5373\u88c5\u5373\u7528":39,"\u539f\u6559\u65e8\u4e3b\u4e49\u8005":39,"\u53bb":39,"\u53c2\u4e0e\u8ba8\u8bba":39,"\u53c2\u8003\u6587\u732e":38,"\u53c8\u7b80\u5355\u660e\u4e86\u5730\u8bbf\u95ee\u6570\u636e\u5e93\u5462":39,"\u53cd\u566c\u7684\u60c5\u666f":39,"\u53e6\u4e00\u65b9\u9762":39,"\u53e6\u5916":39,"\u53ea\u613f\u5b9a\u4e49":39,"\u53ea\u6709\u5f02\u6b65\u6267\u884c\u65f6\u624d\u7528\u5230":39,"\u53ef\u4ee5\u76f4\u63a5\u83b7\u53d6\u5230":39,"\u53ef\u4ee5\u8c28\u614e\u5730\u7528\u4e8e\u751f\u4ea7\u73af\u5883\u4e86":39,"\u53ef\u5b9a\u5236\u5316\u7684\u52a0\u8f7d\u5668":39,"\u53ef\u9009":39,"\u5404\u4e2a":39,"\u5404\u5927\u6d41\u884c\u5f02\u6b65":39,"\u5404\u79cd":39,"\u540c\u65f6":39,"\u540c\u65f6\u4e5f\u4e0d\u9700\u8981\u4e3a\u8fd9\u79cd\u660e\u786e\u6027\u4ed8\u51fa\u8fc7\u5927\u7684\u5de5\u7a0b\u4ee3\u4ef7":39,"\u5462":39,"\u548c":39,"\u54a6":39,"\u54e6\u4e0d":39,"\u56de\u7b54\u95ee\u9898":39,"\u56e0\u4e3a\u7269\u6781\u5fc5\u53cd":39,"\u56e0\u6b64":39,"\u56e0\u6b64\u8fd8\u4e0d\u80fd\u5b8c\u5168\u53d1\u6325":39,"\u5728\u5e26\u6765\u751f\u6d3b\u4fbf\u5229\u7684\u540c\u65f6":39,"\u5728\u6267\u884c\u6548\u7387\u4e0a\u4e5f\u6ca1\u843d\u4e0b":39,"\u5728\u7ebf\u6e38\u620f\u7b49\u9ad8\u5e76\u53d1\u9886\u57df":39,"\u5728\u8fd9\u4e2a\u70e7\u8111\u7684\u5f02\u6b65\u4e16\u754c\u91cc":39,"\u5728\u8fd9\u91cc\u5c31\u4e0d\u4e00\u4e00\u5217\u4e3e\u4e86":39,"\u57fa\u4e8e":39,"\u57fa\u7840\u6559\u7a0b":39,"\u586b\u8865\u4e86\u56fd\u5185\u5916":39,"\u589e\u5220\u6539\u67e5":39,"\u5927":39,"\u5927\u5b66\u91cc\u5f00\u59cb\u5199":39,"\u5927\u91cf\u7684\u6210\u529f\u6848\u4f8b\u4e5f\u8bc1\u660e\u4e86":39,"\u5929\u751f\u538c\u6076":39,"\u5982\u679c\u50cf\u8fd9\u6837":39,"\u5988\u5988\u518d\u4e5f\u4e0d\u7528\u62c5\u5fc3\u6211\u4e0d\u4f1a\u96c6\u6210":39,"\u5b83\u4eec":39,"\u5b83\u4eec\u5173\u6ce8\u7684\u91cd\u70b9\u4e0e":39,"\u5b83\u7684\u5168\u79f0\u662f":39,"\u5b83\u7684\u5b9e\u4f8b":39,"\u5b98\u5ba3":38,"\u5b9a\u4e0b\u4e86\u4e24\u4e2a\u4e1a\u7ee9\u76ee\u6807":39,"\u5b9e\u4f8b":39,"\u5b9e\u4f8b\u7684":39,"\u5b9e\u4f8b\u8bbe\u7f6e\u5230":39,"\u5bf9":39,"\u5bf9\u4e8e\u4e0a\u4e86\u89c4\u6a21\u7684\u5f02\u6b65\u5de5\u7a0b\u9879\u76ee\u6765\u8bf4\u5c24\u4e3a\u91cd\u8981":39,"\u5bf9\u4e8e\u5982\u4f55\u5c06\u6570\u636e\u5e93\u67e5\u8be2\u7ed3\u679c\u7ec4\u88c5\u6210\u5185\u5b58\u5bf9\u8c61\u53ca\u5176\u5c5e\u6027":39,"\u5bf9\u4e8e\u7b80\u5355\u76f4\u89c2\u7684\u4e00\u5bf9\u4e00\u52a0\u8f7d":39,"\u5bf9\u5176\u6267\u884c":39,"\u5bf9\u8c61":39,"\u5bf9\u8c61\u5173\u7cfb\u6620\u5c04":39,"\u5c06\u6570\u636e\u5e93\u8fd4\u56de\u7ed3\u679c\u7684\u6bcf\u4e00\u884c\u4e2d":39,"\u5c0f\u65f6\u5019\u5199":39,"\u5c31\u53d8\u6210\u4e86\u4eba\u540d":39,"\u5c31\u5b9e\u73b0\u4e86":39,"\u5c31\u662f\u4ed6\u4eec\u7684\u4f5c\u54c1":39,"\u5c31\u662f\u5185\u5b58\u91cc\u9762\u7684\u5e38\u89c4\u5bf9\u8c61":39,"\u5c31\u662f\u660e\u786e\u6027\u7684\u5173\u952e":39,"\u5c31\u662f\u6709\u4eba\u53d7\u4e0d\u4e86\u4e86\u81ea\u5df1\u5199\u4e86\u4e00\u4e2a\u7c7b\u578b\u6ce8\u89e3":39,"\u5c31\u6ca1\u6709\u6570\u636e\u5e93\u64cd\u4f5c":39,"\u5c5e\u4e8e":39,"\u5c5e\u6027\u4e0a":39,"\u5c81\u5f00\u59cb\u63a5\u89e6\u7f16\u7a0b":39,"\u5de5\u4f5c\u5934\u4e94\u5e74\u8f6c\u5411\u4e86":39,"\u5df2\u7ecf\u521d\u6b65\u5177\u5907\u53d1\u5e03":39,"\u5e74\u521b\u4f5c\u4e4b\u521d":39,"\u5e74\u751f\u4eba":39,"\u5e76\u4e0d\u662f\u4ece\u5934\u9020\u8f6e\u5b50":39,"\u5e76\u63a5\u89e6\u5230\u4e86":39,"\u5efa\u8bbe\u793e\u4f1a\u4e3b\u4e49":38,"\u5f00\u53d1\u4eba\u5458\u7684\u65f6\u95f4\u786e\u5b9e\u6bd4\u673a\u5668\u7684\u65f6\u95f4\u503c\u94b1":39,"\u5f00\u53d1\u5e94\u7528\u7a0b\u5e8f\u7684\u65f6\u5019\u4e0d\u7528\u62c5\u5fc3\u4f1a\u88ab\u610f\u6599\u4e4b\u5916\u7684\u884c\u4e3a\u6240\u60ca\u5413\u5230":39,"\u5f00\u6e90\u793e\u533a\u91cc\u4e5f\u76f8\u7ee7\u51fa\u73b0\u4e86\u50cf":39,"\u5f00\u7bb1\u5373\u7528\u7684\u6570\u636e\u5e93\u53d8\u66f4\u7ba1\u7406\u5de5\u5177":39,"\u5f02\u6b65":39,"\u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668":38,"\u5f02\u6b65\u7f16\u7a0b\u8fd9\u4e2a\u8bdd\u9898\u4e5f\u5728\u9010\u6e10\u5347\u6e29":39,"\u5f15\u64ce":39,"\u5f15\u64ce\u4e0e\u8fde\u63a5":39,"\u5f62\u4f3c\u800c\u795e\u4e0d\u4f3c":39,"\u5f80\u5f80\u4f1a\u9009\u62e9\u727a\u7272\u660e\u786e\u6027":39,"\u5f88\u7b80\u5355\u7684\u4e00\u4e2a\u5916\u8fde\u63a5\u67e5\u8be2":39,"\u5f97\u5929\u72ec\u539a\u7684\u7075\u6d3b\u6027":39,"\u5feb\u4e50\u5730\u7f16\u7a0b":39,"\u5feb\u6377\u65b9\u5f0f":39,"\u6027\u80fd\u83ab\u540d\u5176\u5999\u7684\u5dee":39,"\u610f\u5473\u7740\u6211\u4eec\u8981\u8df3\u51fa\u53bb\u6267\u884c\u6570\u636e\u5e93\u64cd\u4f5c\u4e86":39,"\u6210\u529f\u5b9e\u73b0\u4e86\u5bf9\u540c\u671f\u7ade\u54c1":39,"\u6211\u5c31\u7ed9":39,"\u6211\u771f":39,"\u6211\u7d22\u6027\u5728":39,"\u6216\u8005\u7528":39,"\u6240\u4ee5":39,"\u6240\u4ee5\u5728":39,"\u6240\u4ee5\u6b22\u8fce\u5927\u5bb6\u4e00\u8d77\u6765\u5efa\u8bbe":39,"\u6240\u4ee5\u8fd9\u4e9b\u529f\u80fd\u4f1a\u9646\u7eed\u5728":39,"\u6240\u6709\u4eba":39,"\u6240\u6709\u8fd9\u4e9b\u95ee\u9898\u5982\u679c\u518d\u653e\u8fdb\u5f02\u6b65\u7f16\u7a0b\u7684\u73af\u5883\u91cc":39,"\u624b\u6cd5":39,"\u6267\u884c":39,"\u627e":39,"\u6307\u54ea\u513f\u6253\u54ea\u513f":39,"\u6362\u53e5\u8bdd\u8bf4":39,"\u63d0":39,"\u63d0\u5347\u4e86\u5199\u4ee3\u7801\u7684\u5e78\u798f\u6307\u6570":39,"\u63d0\u5efa\u8bae":39,"\u6570\u636e\u5e93\u4e8b\u52a1":39,"\u6570\u636e\u5e93\u4e8b\u52a1\u5c01\u88c5\u548c\u5d4c\u5957":39,"\u6587\u5a31":39,"\u65b0\u5a92\u4f53":39,"\u65b9\u4fbf\u5feb\u6377":38,"\u65b9\u8a00\u548c\u9a71\u52a8\u7684\u96c6\u6210":39,"\u65e0\u989d\u5916\u4f9d\u8d56\u5173\u7cfb":39,"\u65e2\u7b80\u5355\u53c8\u660e\u4e86\u6709\u6ca1\u6709":39,"\u660e\u786e\u6027":39,"\u662f":39,"\u662f\u4e00\u4e2a":39,"\u662f\u4e00\u4e2a\u5f00\u6e90\u9879\u76ee":39,"\u662f\u4e00\u7c7b\u5f00\u53d1\u4eba\u5458\u559c\u95fb\u4e50\u89c1\u7684\u6548\u7387\u5de5\u5177":39,"\u662f\u4e0d\u662f\u5341\u5206\u65b9\u4fbf":39,"\u662f\u5b8c\u5168\u65e0\u72b6\u6001\u7684\u666e\u901a":39,"\u662f\u7528\u6765\u8bbf\u95ee\u6570\u636e\u5e93\u7684":39,"\u662f\u7684":39,"\u662f\u8c01":38,"\u66f4\u591a\u7684\u4f8b\u5b50\u548c\u6587\u6863":39,"\u66f4\u91cd\u8981\u7684\u662f\u5e26\u6765\u4e86\u6574\u4e2a":39,"\u6700\u540e":39,"\u6700\u540e\u4e5f\u662f\u6700\u91cd\u8981\u7684":39,"\u6700\u540e\u5c06":39,"\u6700\u5927\u7a0b\u5ea6\u7684\u4fbf\u5229":39,"\u6700\u5c11\u4fb5\u5165\u578b":39,"\u6709\u6ca1\u6709\u529e\u6cd5\u53ef\u4ee5\u65e2\u65b9\u4fbf\u5feb\u6377":39,"\u670d\u52a1":39,"\u671f\u95f4\u8d21\u732e\u4e86":39,"\u6765\u4fee\u6539\u5c5e\u6027":39,"\u6765\u521b\u5efa\u65b0\u7684\u5b9e\u4f8b":39,"\u6765\u6362\u53d6\u4fbf\u6377\u6027":39,"\u6781\u5927\u5730":39,"\u67d0\u4e9b\u573a\u666f\u4e0b":39,"\u6846\u67b6":39,"\u6846\u67b6\u4e86":39,"\u6846\u67b6\u63d2\u4ef6\u7684\u7ef4\u62a4\u5de5\u4f5c\u9700\u8981\u591a\u4eba\u8ba4\u9886":39,"\u6846\u67b6\u6765\u8bf4\u662f\u4e0d\u53ef\u63a5\u53d7\u7684":39,"\u6846\u67b6\u7684\u5b9a\u5236\u7248\u63d2\u4ef6":39,"\u6b22\u8fce\u6765\u5230":39,"\u6b63\u72b9\u5982":39,"\u6bd4\u5982\u4e00\u4e2a\u7528\u6237\u53ef\u80fd\u5199\u4e86\u5f88\u591a\u672c\u4e66":39,"\u6bd4\u5982\u6267\u884c":39,"\u6bd4\u5982\u6ca1\u6709\u7167\u987e\u5230":39,"\u6bd4\u5982\u7528":39,"\u6bd4\u5982\u8bf4":39,"\u6c7d\u8f66\u7b49\u884c\u4e1a\u7684\u8d77\u8d77\u4f0f\u4f0f":39,"\u6ca1\u6709":39,"\u6ca1\u6709\u53d1\u7968":39,"\u6ca1\u6709\u9519":39,"\u6df1\u53d7\u4fc4\u7f57\u65af\u548c\u4e4c\u514b\u5170\u4eba\u6c11\u7684\u7231\u6234":39,"\u706b\u529b\u5168\u5f00\u578b":39,"\u7136\u540e\u5b9a\u5236\u52a0\u8f7d\u5668\u81ea\u52a8\u52a0\u8f7d\u6210\u671f\u671b\u7684\u5bf9\u8c61\u5173\u7cfb":39,"\u7136\u540e\u5c06\u8be5\u884c\u4e2d\u5269\u4e0b\u7684\u5c5e\u4e8e":39,"\u7136\u540e\u8fd9\u6837\u6765\u52a0\u8f7d\u8fd9\u79cd\u591a\u5bf9\u4e00\u5173\u7cfb":39,"\u7248\u672c\u4e2d\u8ddf\u4e0a":39,"\u7279\u522b\u611f\u8c22":39,"\u751f\u957f\u7684\u6e29\u5e8a":39,"\u7528":39,"\u7528\u6237\u5305":39,"\u7535\u5546":39,"\u7684":39,"\u7684\u540c\u5b66\u6765\u8bf4\u53ef\u80fd\u5e76\u4e0d\u964c\u751f":39,"\u7684\u57fa\u7840\u4e0a\u5f00\u53d1\u7684":39,"\u7684\u589e\u5f3a\u63d2\u4ef6":39,"\u7684\u590d\u6742\u5ea6\u4e86":39,"\u7684\u5b57\u6bb5\u52a0\u8f7d\u6210\u4e00\u4e2a":39,"\u7684\u5b66\u4e60\u66f2\u7ebf\u524d\u5e73\u540e\u9661":39,"\u7684\u5b9a\u4e49\u98ce\u683c":39,"\u7684\u5e94\u7528\u6848\u4f8b":39,"\u7684\u5e95\u5c42\u6838\u5fc3":39,"\u7684\u5f3a\u529b\u52a0\u6301":39,"\u7684\u5f88\u591a\u8bbe\u8ba1\u90fd\u53d7\u5230\u4e86\u660e\u786e\u6027\u7684\u5f71\u54cd":39,"\u7684\u652f\u6301":39,"\u7684\u6587\u6863":39,"\u7684\u6700\u5927\u4f18\u52bf\u8fd8\u662f\u5728\u4e8e\u5145\u5206\u5e73\u8861\u4e86\u5f00\u53d1\u6548\u7387\u548c\u660e\u786e\u6027\u4e4b\u95f4\u7684\u8fa9\u8bc1\u77db\u76fe\u5173\u7cfb":39,"\u7684\u6f5c\u80fd":39,"\u7684\u751f\u6001\u73af\u5883":39,"\u7684\u7528\u6237\u5bf9\u8c61":39,"\u7684\u7acb\u573a":39,"\u7684\u7c7b\u578b\u63d0\u793a":39,"\u7684\u8d21\u732e":39,"\u7684\u9012\u5f52\u5b9a\u4e49":39,"\u7684\u964d\u7ef4\u6253\u51fb":39,"\u76ee\u524d\u4e5f\u662f\u4e0d\u652f\u6301\u7684":39,"\u76ee\u524d\u5728\u829d\u5927\u7ee7\u7eed\u505a\u751f\u7269\u5927\u6570\u636e\u76f8\u5173\u7684\u5f00\u6e90\u9879\u76ee":39,"\u76ee\u524d\u6025\u9700\u5e2e\u52a9\u7684\u6709":39,"\u76ee\u524d\u652f\u6301\u4e09\u79cd\u4e0d\u540c\u7a0b\u5ea6\u7684\u7528\u6cd5":39,"\u76ee\u524d\u7684\u4e0d\u8db3\u4e4b\u5904\u8fd8\u6709\u4e00\u4e9b":39,"\u77ff\u5708":39,"\u793e\u4ea4":39,"\u7a0d\u6709\u4e0d\u540c":39,"\u7a33\u5b9a\u7248\u53d1\u5e03\u7684\u524d\u5915\u505a\u4e2a\u5e74\u7ec8\u603b\u7ed3":39,"\u7a33\u5b9a\u7248\u7684\u5404\u79cd\u6761\u4ef6":39,"\u7a7a\u624b\u63a5":39,"\u7b14\u8005\u5de5\u4f5c\u4e0a\u5f00\u53d1\u7684\u4e00\u4e2a\u670d\u52a1":39,"\u7b49":39,"\u7b49\u4f18\u79c0\u7684\u5f02\u6b65":39,"\u7b49\u5230\u9700\u8981\u64cd\u4f5c\u6570\u636e\u5e93\u7684\u65f6\u5019":39,"\u7b49\u591a\u9879\u4fbf\u6377\u529f\u80fd":39,"\u7b49\u5f00\u6e90\u9879\u76ee\u4e14\u4e00\u53d1\u4e0d\u53ef\u6536\u62fe":39,"\u7b49\u6846\u67b6\u7684\u9646\u7eed\u6d8c\u73b0":39,"\u7b49\u9879\u76ee\u4e86\u89e3\u4e86\u5f02\u6b65\u7f16\u7a0b":39,"\u7b80\u5355\u660e\u4e86":38,"\u7c7b":39,"\u7c7b\u4f3c":39,"\u7cbe\u51c6\u63a7\u5236\u52a0\u8f7d\u884c\u4e3a":39,"\u7ec8\u8eab\u4e0d\u5a5a\u578b":39,"\u7edd\u4e0d\u662f\u9ed1\u54c8":39,"\u7edd\u5bf9\u7eff\u8272\u73af\u4fdd\u65e0\u6bd2\u526f\u4f5c\u7528":39,"\u7ef4\u57fa\u767e\u79d1":39,"\u7ef4\u62a4\u793e\u533a":39,"\u800c":39,"\u800c\u5168\u6743\u4ea4\u7ed9\u7528\u6237\u6765\u660e\u786e\u5730\u5b9a\u4e49":39,"\u800c\u662f\u5728":39,"\u804a\u5929\u673a\u5668\u4eba":39,"\u80fd\u53eb\u4e0a\u540d\u5b57\u7684\u50cf":39,"\u80fd\u5728\u5feb\u901f\u539f\u578b\u5f00\u53d1\u4e2d\u5927\u5c55\u8eab\u624b":39,"\u81ea\u7136\u662f\u4f3a\u5019\u5230\u5bb6\u7684":39,"\u867d\u7136\u6587\u6863\u8fd8\u5728\u52aa\u529b\u7f16\u5199\u4e2d":39,"\u867d\u7136\u662f":39,"\u8868":39,"\u8868\u7ed3\u6784\u5b9a\u4e49":39,"\u88ab\u5e7f\u6cdb\u5e94\u7528\u4e8e\u8bf8\u5982\u5b9e\u65f6\u6c47\u7387":39,"\u8981\u7528":39,"\u8bbf\u95ee\u5c5e\u6027":39,"\u8dd1\u8d77\u6765\u4e5f\u662f\u53ef\u4ee5\u98de\u5feb\u7684":39,"\u8f7b\u91cf\u7ea7":39,"\u8fc1\u79fb":39,"\u8fd8\u4f1a\u5076\u5c14\u4fee\u4e00\u4fee":39,"\u8fd8\u4f1a\u8fd4\u56de\u4e00\u4e2a\u5305\u542b\u672c\u6b21\u53d8\u66f4\u7684\u4e2d\u95f4\u7ed3\u679c":39,"\u8fd8\u63d0\u4f9b\u4e86":39,"\u8fd8\u662f\u7b14\u8005\u81ea\u5df1\u5199\u7684\u4e00\u4e2a\u5de5\u5177":39,"\u8fd8\u6709\u51e0\u4e2a\u5546\u7528\u7684":39,"\u8fd8\u6709\u5f88\u591a\u7c7b\u4f3c\u7684\u7279\u6027":39,"\u8fd8\u8d34\u5fc3\u5730\u63d0\u4f9b\u4e86\u4e2d\u6587\u6587\u6863":39,"\u8fd9\u4e2a\u9879\u76ee\u5c31\u53eb":39,"\u8fd9\u4e48\u505a\u9664\u4e86\u80fd\u4fdd\u6301\u719f\u6089\u7684\u5473\u9053":39,"\u8fd9\u4e48\u5b9a\u4e49\u8868\u7ed3\u6784\u751a\u81f3\u8ba9\u4eba\u6709\u70b9\u5c0f\u5174\u594b":39,"\u8fd9\u4e9b\u64cd\u4f5c\u90fd\u4e0d\u4f1a\u8bbf\u95ee\u6570\u636e\u5e93":39,"\u8fd9\u4ee3\u7801\u4f60\u8ba9\u6211\u600e\u4e48\u8c03\u8bd5":39,"\u8fd9\u5bf9\u4e8e\u4e00\u6b3e\u4f18\u79c0\u7684\u5f02\u6b65":39,"\u8fd9\u5c31\u662f":39,"\u8fd9\u662f\u8c01":39,"\u8fd9\u91cc\u7684":39,"\u8fde\u63a5\u6c60\u7ba1\u7406\u548c\u61d2\u52a0\u8f7d":39,"\u901a\u8fc7":39,"\u90a3\u4e3a\u4ec0\u4e48\u975e\u8bf4":39,"\u90a3\u5c31\u662f":39,"\u90e8":39,"\u90fd\u662f\u53ea\u5728\u5185\u5b58\u91cc\u4fee\u6539\u5bf9\u8c61\u7684\u5c5e\u6027":39,"\u90fd\u6709\u53ef\u80fd\u89e6\u53d1\u4e00\u5927\u5806\u610f\u60f3\u4e0d\u5230\u7684\u6570\u636e\u5e93\u8c03\u7528":39,"\u914d\u5408\u4e00\u4e2a\u76f4\u89c2\u7684\u52a0\u8f7d\u5668":39,"\u91cc\u7684\u5bf9\u8c61\u6620\u5c04\u4e0d\u80fd\u4e22":39,"\u91cd\u89c6\u5f00\u53d1\u6548\u7387\u7684\u6982\u5ff5\u5bf9\u4e8e\u5199":39,"\u957f\u671f\u6d3b\u8dc3\u7684\u8d21\u732e\u8005\u8fd8\u80fd\u83b7\u8d60\u4ef7\u503c":39,"\u968f\u4fbf\u4e00\u53e5":39,"\u968f\u7740":39,"\u968f\u7740\u8fd9\u51e0\u5e74":39,"\u975e\u5178\u578b\u5f02\u6b65":39,"\u9879\u76ee\u5916":39,"\u9879\u76ee\u6216\u591a\u6216\u5c11\u90fd\u4f1a\u9047\u5230":39,"\u9879\u76ee\u7684\u8d21\u732e":39,"\u9886\u57df\u7684\u7a7a\u767d":39,"\u9ad8\u6027\u80fd\u6a21\u677f\u9879\u76ee":39,"abstract":[3,21,29,37],"boolean":[9,25,29],"break":[1,13,37,40],"case":[2,3,5,8,10,11,12,13,19,37],"class":[2,5,8,9,10,11,12,19,20,21,23,24,25,28,29,30,31,32,34,37,39,40,41],"default":[2,3,5,6,9,10,11,12,13,19,20,21,23,25,28,29,34,35,37,40,41],"enum":[23,37],"export":[5,6],"final":[1,2,3,8,25,37,40,41],"float":9,"function":[2,6,9,18,23,25,29,40],"ila\u00ef":37,"import":[1,2,3,5,8,9,10,11,12,18,19,21,27,29,31,34,35,37,39,40,41],"int":[9,13,34,40],"long":[1,2,3,13,25,34,35],"micha\u0142":37,"new":[1,2,3,5,6,8,10,12,19,20,21,25,29,31,34,37,38,41],"null":[2,37],"public":12,"ram\u00edrez":39,"return":[1,2,3,8,9,10,12,13,19,20,21,23,25,29,31,34,35,37,40,41],"sebasti\u00e1n":39,"short":[2,3,12],"static":[24,40],"super":[3,10],"switch":[1,2,19,37],"transient":2,"true":[2,3,5,8,9,10,12,13,19,20,21,23,24,25,29,30,34,35,39,40,41],"try":[1,2,3,8,13,25,32,37,40,41],"var":6,"while":[1,2,3,8,10,12,19,20,25,32,37,41],"za\u0165ko":37,Added:37,And:[1,2,5,6,9,10,20,37,40,41],Being:3,But:[1,2,3,10,40],Doing:3,For:[1,2,3,6,8,10,12,19,20,25,29,32,37,40,41],IDE:39,IDs:10,INTO:[13,41],NOT:[10,21],Not:[8,14,21,29,39,41],One:[1,4,5,25],PRs:40,That:[1,2,3,5,10,13,34,40,41],The:[0,2,3,6,8,9,10,11,12,13,19,20,21,23,25,29,32,34,35,37,39,40,41],Their:21,Then:[2,3,6,10,12,25,40,41],There:[1,2,3,11,12,25,37,41],These:[3,37],Use:[4,8,20,41],Used:[21,37],Using:[8,9,10,34],WITH:6,Will:40,With:[1,2,3,10,20,40,41],Yes:2,__all__:37,__attr_factory__:21,__init__:[10,40],__main__:34,__metadata__:21,__model__:37,__name__:[21,34,40],__repr__:34,__table__:[12,20,21],__table_args__:[21,37,41],__tablename__:[5,8,9,10,12,21,34,37,39,40,41],__values__:21,_base:23,_bind:8,_child:10,_children:10,_engin:23,_event:23,_idx1:41,_idx2:41,_is_metadata_oper:30,_name_idx:8,_parent:10,_pk:41,_schematranslatemap:25,_test:40,_update_request_cl:37,abandon:37,abcd:6,abil:[10,37],abl:[3,40],abnormal_detect:9,abort:34,about:[1,2,3,5,6,8,12,13,20,25,37,41],abov:[1,2,3,8,10,29,40,41],absolut:[2,3],accept:[2,19,20,23,25,37,41],access:[0,2,8,12,13,21,25,32,34,37,40,41],access_log:9,accid:2,accord:[3,25,29,35],achiev:[1,8,10,12,19,25],acquir:[2,3,12,19,23,24,25,32,34,37],acquisit:3,across:[29,40],act:[1,2,41],action:[6,32],activ:[6,25,40],actual:[1,2,3,10,12,13,20,21,25,34,40,41],adapt:41,add:[1,2,3,5,6,8,10,19,20,27,37,38,41],add_child:10,add_us:40,added:[19,21,25,40,41],addit:[1,2,9,10,29],addition:[3,20],address:[5,12],adjac:8,adjust:40,admin:4,adopt:37,advanc:4,advic:3,affect:[20,37,41],after:[1,2,3,5,12,13,19,20,23,25,29,34,37,40,41],after_get:9,afterward:23,again:[1,2,3,8,10,25,40,41],against:6,age:[9,20,32],age_idx:9,aggreg:20,aintq:39,aiocontextvar:[2,4,15,16,17,37,39],aiohttp:[37,39],alemb:[4,9,12,19,38,39,41],alembic_sampl:5,ali:8,alia:[8,10,19,20,23,24,25,29,31,37],aliasload:29,alik:37,all:[1,2,3,5,6,8,9,10,12,13,19,20,21,24,25,29,32,34,35,37,40,41],all_us:41,allow:[1,2,12,19,21,23,37,40,41],alon:2,alpha:[8,37],alpin:[6,40],alreadi:[1,3,10,21,41],alright:1,also:[1,2,3,5,8,10,12,13,19,20,21,23,25,27,29,37,40,41],altern:[2,8,11,25,40,41],although:10,alwai:[2,5,6,8,12,19,20,25,29,32,34,37,40,41],amaz:[8,41],amount:2,analysi:40,andrei:39,ani:[1,2,3,5,6,8,19,21,23,25,27,29,37],anoth:[1,2,3,10,20,25,29,41],answer:[1,8],anyth:[3,6,12,25,37],api:[2,3,8,12,15,17,23,25,32,38,39,41],apirout:40,apk:40,app:[3,8,11,34,35,37,40],appear:1,append:20,append_where_primary_kei:20,appli:[2,4,12,20,25,32,39,40,41],applic:[3,8,19,35,37,40,41],appreci:6,approach:[1,3,8],arbitrari:3,archlinux:39,arg:[17,19,20,21,23,24,25,30,32],argument:[2,8,10,19,20,23,25,29,31,35,37,41],around:[25,39],arq:39,arrai:[9,23,37],arrayproperti:[9,28],arriv:1,arrrrh:1,articl:6,artifact:40,asap:1,ascend:10,ascii_lett:[8,10],asgi:40,ask:[2,4],assembl:[2,10],assert:[13,21,32,37,40,41],assertionerror:37,assign:2,assist:10,associ:[2,12],assum:[1,3,40],assumpt:32,async:[1,2,3,8,10,12,13,19,20,23,24,25,30,31,32,34,37,39,40,41],async_execut:[23,24],asyncdialectmixin:[23,24],asyncenum:23,asynchron:[0,2,10,12,13,14,19,25,32,41],asyncio:[0,1,2,4,10,12,14,18,39,41],asyncpg:[2,3,8,11,12,13,14,15,16,17,22,25,31,35,37,39,40,41],asyncpg_deleg:37,asyncpgcompil:23,asyncpgcursor:23,asyncpgdbapi:23,asyncpgdialect:23,asyncpgexecutioncontext:23,asyncpgiter:23,asyncpgjsonpathtyp:23,asyncpgsa:[12,39],asyncschemadropp:30,asyncschemagener:30,asyncschematypemixin:30,asyncvisitor:30,ath:20,atom:20,attack:40,attent:34,attribut:[8,10,12,20,21,25,29,37,41],attributeerror:19,audienc:41,audit_profil:9,aur:39,austin:8,authent:3,author:[3,19,39,40],author_id:39,auto:[9,20,21,40],autocommit:3,autogener:[5,40],autom:[10,40],automat:[10,18,20,21,25,29,31,32,34,40],avail:[2,8,12,13,20,21,25,32,35,37,40],averchenkov:37,avoid:[3,29],awai:[2,3],await:[1,2,3,8,9,10,12,13,19,20,25,29,32,34,35,37,39,40,41],awar:40,awesom:[1,39],back:[1,2,3,10,13,19,25,32,37],backend:40,background:14,backport:[2,8,37,40],backward:37,bad:37,balanc:[3,20],bar:1,barancsuk:37,bare:[1,3],base:[5,8,10,11,15,16,17,19,20,21,22,23,25,26,28,29,30,31,32,40],base_exp:28,basedbapi:[23,24],baseexcept:[13,32],basemodel:40,basic:[2,3,4,20,38,39],batch:[4,20,41],bayer:[3,39],becaus:[1,2,3,8,12,13,19,20,32,37,40,41],becom:[2,12],been:[1,2,23,37],befor:[1,2,3,6,8,10,12,13,20,25,32,34,40,41],before_set:9,begin:[1,3,23,24],beginn:41,behav:[20,37],behavior:[2,8,13,20,25,34,37],behind:[2,3,10,20,37,40],being:[1,2,3,12,20],belong:13,below:[6,8,9,40],benefici:3,besid:8,best:[1,6,37,40],beta:37,better:[3,37,39],between:[1,2,3,37],beyond:3,biginteg:[19,34,40],bin:40,binari:[24,37],bind:[2,3,8,12,13,19,20,23,30,37,41],bind_processor:[2,23],bindtempl:23,binghan:37,birthdai:9,bit:[1,6,12,20,37,41],bite:[25,34],black:37,blade:1,blob:39,block:[1,2,3,13,25,32,34],blog:6,blue:2,bondar:39,book:[8,19,39,41],booker:41,bookings_idx_booker_room:41,bookings_idx_day_room:41,bookings_pkei:41,bool:[9,40],booleanproperti:[9,28],boost:1,borrow:[2,13,25,34,35],boss:1,bot:39,both:[2,3,8,10,12,19,20,21,23,32,37,41],bottleneck:[1,3],bound:[1,19,20,21,34,41],branch:6,brien:37,broken:37,browser:6,brutal:1,bryanforb:39,bsd:14,buffer:3,bug:[4,8,37,39],bugfix:6,build:[1,3,6,8,10,19,20,38],builder:[10,40],built:[8,12,14,20,21,25,35,37,40,41],builtin:[24,34,37],bulk:[3,4,25],bunch:5,busi:[1,3,12],c10k:1,cach:40,call:[1,2,3,8,10,13,18,19,20,21,23,25,29,32,37,40,41],call_next:8,callabl:[10,23,25,29,37],callableload:29,callback:1,caller:8,came:3,can:[1,2,3,4,5,6,10,11,12,13,19,20,21,25,29,32,34,35,37,40,41],candid:37,cannot:[1,2,10,37,40],canopi:39,canopytax:39,cap:3,captur:40,carefulli:1,cast:[9,40],categori:10,categories_1:10,categories_2:10,caught:32,caus:[2,3,13,25,34,35,37,41],cdi:39,celeri:3,certain:[3,34],chain:[2,3,19,20,29,37,41],challeng:3,chanc:[1,3],chang:[5,6,8,20,25,37,40,41],chat:3,check:[2,6,8,10,12,20,23,40,41],checkfirst:[23,30],checklist:40,checkout:6,child:[10,37],child_id:10,children:[10,32],chmod:6,choic:[3,8,10],choos:[3,37,41],classic:10,classmethod:[20,24,29],claus:[2,8,10,12,19,20,23,24,25,29,41],clean:[8,37,40],cleaner:40,cleanli:[3,25],cleanup:35,clear:3,cli:40,click:2,client:[3,6,39,40],clone:6,close:[2,3,13,19,23,24,25,32,34,37,41],cls:[9,21],cmd:40,code:[1,2,3,8,10,12,13,14,25,32,37,40,41],coin:3,col:10,collect:[20,40],collid:37,color:[2,23,24,25],colspec:23,coltyp:23,column:[2,4,5,9,10,12,19,20,21,23,25,29,34,37,39,40,41],column_kei:23,column_nam:20,columnattribut:21,columnload:[8,10,29],com:[6,8,14,39,40],combin:[2,10,41],come:12,command:[3,5,40,41],command_timeout:23,comment:25,commit:[3,6,13,23,24,25,32,37,40],common:[12,13,25,35,40],commun:[14,39],compani:29,compar:[1,3,20,40],compat:[2,8,25,29,37],compil:[19,24,25,41],complet:[2,8,19,37,40],complex:[1,4,10,39,41],complic:1,compos:[40,41],composit:20,comput:1,con:0,concentr:3,concept:[2,12,41],conceptu:12,conclus:3,concret:[2,12,21,34],concurr:[1,3],condit:[10,20,41],config:[8,11,34,35,37,40],configur:[6,15,29,33,34,40],confirm:37,conflict:[10,29],conftest:40,confus:2,congratul:5,conn1:2,conn2:2,conn:[2,3,12,19,23,24,25,32,37],connect:[0,3,4,10,12,13,15,19,23,25,30,32,33,34,37,38,40],connection_cl:25,connection_class:23,connectionless:2,conradi:37,consid:[3,8,37],consist:37,constant:1,constraint:[21,30,37,41],construct:[3,8,12,19,21],consum:3,contain:[5,8,21,41],content:[1,15,16],context:[1,2,3,8,13,14,19,23,24,25,29,32,34,35,37,39,40],contextu:[8,10,34],contextualgino:8,contextvar:[2,8,15,18,39],continu:[13,32],contrast:[1,3],contribut:[4,37],control:[1,4,23,25,34,37,40],conveni:[2,3,12,13,19,20,39,40,41],convers:[2,23],convert:29,cool:1,cooper:[0,3],copi:[8,25,37,40],core:[1,2,4,8,14,19,21,37,39,41],coroutin:[1,2,3,20,25,31],correctli:[3,12,13,19,20,37,40],correspond:[2,10,20,21],correspondingli:[2,13,32],cost:1,could:[1,2,3,6,8,10,20,40],count:[2,8,10,40,41],count_1:41,cours:40,cov:40,cover:[37,40,41],coverag:[37,40],cpu:1,cpython:39,creat:[0,3,4,6,8,10,12,13,19,20,21,23,25,29,30,31,32,34,35,37,38,39],create_al:[8,9,10,12,19,30,37,41],create_async:[23,30],create_engin:[2,3,8,11,12,17,19,25,29,31,37,41],create_ok:30,create_pool:[2,37],create_t:40,create_task:8,createdb:[40,41],creation:[2,8,19,37,40],credenti:5,credit:6,critic:1,cross:34,crt:6,crucial:13,crud:[2,3,4,8,10,12,15,16,17,19,25,37,38,39],crudmodel:[19,20,37],csrf:40,ctrl:40,ctx:10,cur:40,curatedlist:39,current:[1,2,8,11,13,17,20,23,25,37,40,41],current_connect:[0,25,37],current_databas:8,current_us:[3,39],cursor:[2,23,24,25],cursor_cl:[23,24],custom:[9,10,20,21,25,37],customiz:[21,37],cut:3,cutil:37,cve:40,dahlia:39,dai:41,daisi:[9,21,39,41],damn:1,danger:34,darwin:40,data:[1,2,8,9,10,20,40,41],databas:[0,2,4,5,6,9,10,12,13,19,20,21,23,25,29,32,34,35,37,39,40,41],datastructur:40,date:41,datetim:[8,9,10,21,29],datetimeproperti:[9,28],db_age:20,db_databas:[34,40],db_driver:40,db_dsn:40,db_echo:[8,34,37,40],db_host:[11,34,40],db_kwarg:[11,34],db_name:[5,6],db_pass:6,db_password:[34,40],db_pool_max_s:[34,40],db_pool_min_s:[34,40],db_port:[6,34,40],db_retry_interv:40,db_retry_limit:40,db_ssl:40,db_use_connection_for_request:[34,40],db_user:[6,34,40],dbapi:[23,24],dbapi_class:[23,24],dbapi_conn:23,dbapicursor:[23,24],dbname:[8,40],ddl:[30,40],dead:3,deadlock:3,deal:[1,3,8,10,13],debug:[1,34],decim:37,declar:[1,4,10,15,16,17,19,20,38],declarative_bas:21,declared_attr:[9,21,37,41],decod:28,decor:21,decreas:1,def:[1,2,3,8,9,10,12,21,23,34,40,41],defaultdialect:23,defer:3,defin:[4,5,9,10,11,12,19,21,25,29,40,41],definit:[8,10,21,40],del:8,delai:3,deleg:[2,12,19,20,37],delet:[10,12,20,37,38,40],delete_us:40,deliv:1,demo:40,demonstr:40,depend:[1,2,3,5,8,20,25,32,37,38,41],deploi:[3,40],deprec:[20,29,37],describ:[3,8,14],descript:[5,6,23,24,35,40],design:[2,3,8,41],detach:41,detail:[2,6,8,10,41],detect:40,determin:[3,10],deutel:37,dev:[39,40],develop:[5,6,15,34,40],diagram:[1,2,40],dialect:[2,8,9,11,14,15,16,17,25,30,32,35,37,41],dict:[2,8,9,11,20,21,29,37,40],dictionari:[2,25,34],did:37,didn:[2,3,27],differ:[1,2,3,4,9,10,12,19,20,21,25,37,40,41],difficult:1,dig:12,direct:[3,25,41],directli:[2,3,8,10,12,19,20,21,25,34,37,40,41],directori:[5,37,40],dirti:[8,37],disabl:[20,37],disable_inherit:37,disable_task_loc:37,disadvantag:3,disallow:8,disast:[13,34],discard:[2,20,25],disconnect:41,discord:39,discourag:3,discuss:8,disproportion:3,dist:37,distinct:[10,20,29,37],distinguish:2,divio:14,django:4,do_load:29,do_on_connect:23,doc:[5,6,8,13,37,39,40],docker:[6,40],dockerfil:40,docstr:6,document:[2,4,5,8,37,39,40,41],doe:[2,3,4,10,12,19,20,25,29,37,39,41],doesn:[2,3,8,9,10,21,37,40,41],doing:[1,3,19,37],don:[0,1,2,8,10,13,20,25,40,41],done:[0,1,2,5,6,8,12,13,37,40,41],doubl:1,doubt:10,down:[3,40],downgrad:[5,40],download:14,dramat:1,driven:6,driver:[2,8,13,23,25,35,37,41],drivernam:40,drop:[1,30,37,40],drop_al:[8,10,30],drop_async:[23,30],drop_ok:30,drop_tabl:40,dsn:[35,37,40],due:[3,37,41],dure:[1,2,21,25,34,37],dynam:[10,12,21],dziewulski:37,each:[1,2,3,5,8,10,21,25,29,34,41],earli:[13,14,15,32,35],earlier:[8,35],easi:[2,8,10],easier:[1,6,8],easili:[1,2,3,10],echo:[2,8,25,35,37,40],ecosystem:3,edg:1,edit:9,effect:[20,25,41],effici:[1,2],effort:3,either:[1,2,3,5,8,12,13,20,25,32,40],elem:[19,24],element:19,els:[2,3,8,12,13,25,34,40,41],email:[3,8],email_address:12,emerg:37,empti:[2,34,35,37,40],enabl:[2,6,8,20,21,29,35,37,40],enable_inherit:37,enable_task_loc:37,encapsul:[3,8,40],encod:[28,39],encourag:19,encrypt:6,end:[2,3,10,13,40,41],endpoint:3,enforc:[10,13],engin:[0,1,4,13,15,16,17,19,20,31,32,35,39,40,41],engine_cl:31,engine_from_config:19,enginestrategi:31,enhanc:[3,6,37],enjoi:34,enough:[3,40],ensur:37,enter:[13,19,25],entri:[12,27,40],entry_point:40,env:[5,8,40],environ:[6,37,40],equal:[2,20,37],equival:[20,21],error:[8,23,24,37],especi:[1,2,3,8,10,37,41],establish:34,etc:[23,40],even:[1,2,3,6,8,12,19,20,21,25,34,37,40,41],event:[1,3,6,23,37,39],eventlet:39,eventu:[2,8,40],ever:32,everi:[1,6,10,20],everyon:2,everyth:[2,3,8,12,13,34,41],exactli:[2,10,19,25,41],exampl:[1,2,3,5,6,8,10,11,12,13,19,20,25,29,32,34,37,40,41],except:[1,2,8,13,15,16,17,23,24,25,32,34,35,37],exchangeratesapi:39,exec:6,execut:[0,3,4,9,10,12,19,20,23,24,25,29,35,37,41],executemani:[24,25,37],execution_ctx_cl:23,execution_opt:[2,8,10,19,20,25,29,37],executioncontextoverrid:[23,24],exhaust:[2,3],exist:[2,3,4,19,20,21,23,35,37,41],exit:[13,19,25,32],exp:9,expect:29,experiment:[10,20,29],explain:[3,6,8,10,14,40],explan:[14,39],explicit:[0,2,8,10,13,37,39,41],explicitli:[2,3,8,9,32,34,41],explict:12,expos:[12,19,37],express:[4,9,20,25,29],ext:[8,11,15,16,17,19,34,35,37,40],extens:[2,8,11,12,15,19,27,34,35,37,38,41],extern:[1,41],extra:[1,29,37,40],extract:37,extrem:2,f2c273a43a3ed893a767d4239046f2befabf510d:39,face:3,facebook:39,fact:[8,23],factori:[21,35,40],fail:[2,19,37,40],fair:1,fals:[2,3,9,12,20,23,24,25,30,34,35,37,40],familiar:41,famou:1,fantix:[20,39,40,41],far:[12,41],fast:[3,34,39,40],fastapi:[3,8,38,39],faster:41,featur:[2,4,10,20,29,37,40,41],fed:[10,37],feed:[1,25,29,40],feedback:4,feel:[1,20,25,40,41],fetch:[1,25],few:[2,3,8,25,34,37],field:[8,9,12,20],file:[1,5,6,37,40],fill:29,filter:[20,41],find:[3,5,21,27,41],fine:[3,8,12],finish:[1,2,3,5,20,25,34,40],first:[1,2,3,4,6,8,10,12,19,20,23,24,25,29,34,35,37,40,41],first_connect:23,first_nam:8,first_or_404:37,firstli:10,fit:2,five:37,fix:[3,4,8,37,40,41],fixtur:40,flag:[23,35],flake8:6,flat:8,flexibl:[9,10,39],flow:3,flush:3,fly:4,focu:8,folder:5,follow:[2,3,8,9,13,19,29,32,40,41],footprint:1,forc:[1,3],foreign:[8,10,20],foreignkei:[8,10,12,39],forev:[20,25],forget:[2,41],fork:6,format:[8,34],former:2,fortun:3,forward:[10,23,24],found:[2,3,8,10,19,21,37,40,41],foundat:[3,39],founder:41,founding_us:41,fouser:19,framework:[1,8,37,40],free:[3,14,20,25,40,41],freebsd:39,freez:3,frequent:[4,10],friendli:[37,41],from:[1,2,3,5,8,9,10,11,12,13,14,19,20,21,25,29,32,34,35,37,39,40,41],fromclaus:29,frozen:40,full:[2,5,8,40],full_path:5,fulli:[25,40,41],fullnam:12,fun:[3,41],func:[8,10,29,41],fundament:3,further:[2,12,25,41],furthermor:[2,25,34],fut:8,futur:[8,10,40],gain:10,galden:37,garciasilva:39,gbasic:39,gcc:40,gener:[1,2,9,10,20,21,23,29,40],geoalchemi:39,get:[1,2,3,4,8,10,12,14,17,19,20,21,25,29,34,37,38,39,40],get_app:40,get_column:29,get_current_connect:37,get_event_loop:41,get_from:29,get_isolation_level:23,get_loc:37,get_now:2,get_or_404:[34,37,40],get_profil:28,get_raw_connect:25,get_result_proxi:24,get_statusmsg:[23,24],get_us:[34,40],get_vers:17,getattr:40,getlogg:40,getter:19,gevent:39,gino:[2,3,4,5,6,9,10,11,13,15,16,34,35,38],gino_db:6,gino_fastapi_demo:40,gino_fastapi_demo_test:40,gino_starlett:27,ginoconnect:[2,12,13,15,25,32],ginoengin:[2,8,12,13,19,20,25,29,31,32,37],ginoexcept:26,ginoexecutor:[2,19],ginonulltyp:23,ginopool:37,ginoschemavisitor:[12,19,30],ginostrategi:31,ginotransact:[13,25,32,37],git:[6,25,40],github:[6,8,14,39],gitignor:40,gitlab:39,gitter:14,give:[1,3,10,29],given:[2,6,8,10,19,20,21,23,25,29,31,40],global:[12,19,41],gmail:40,gnu:39,goe:41,golden:3,goncharov:37,gone:37,good:[1,25],got:1,gotta:1,grai:2,grammar:[3,8,37],grandson:10,great:[3,5,8],greater:[1,18],greatli:[1,2,6],green:[1,2],greenlet:8,group_bi:[8,10],guarante:[3,13,25,40],guess:3,guess_model:37,guid:[14,40,41],guidelin:4,gunicorn:40,hack:[8,40,41],had:[1,12,21],hadn:1,hand:[1,2],handl:[1,2,3,13,25,32,37],handler:34,hang:3,happen:[1,3,19,25,41],happi:40,hard:3,hardwar:3,harm:3,has:[1,2,3,8,10,12,13,19,20,23,25,37,41],has_schema:23,has_sequ:23,has_tabl:23,has_typ:23,haunt:3,have:[1,2,3,5,8,10,12,13,20,29,32,35,39,40,41],haven:40,head:[2,5,40],hei:[1,3],height:9,help:[0,1,6,13,41],henc:19,here:[1,2,3,6,8,9,10,12,13,20,25,32,37,40,41],herebi:19,hidden:2,hide:[2,37],hierarch:[8,10],hierarchi:40,high:[1,3],higher:40,histori:15,hit:3,hmm:2,hold:[1,8,13,40],holubakha:37,hong:39,honor:40,hood:[2,20,41],hook:[4,8,19,23,27],host:[23,35,40],how:[0,1,2,5,6,10,13,14,25,39,41],howev:[1,2,3,8,12,19,20,25,37],html:39,http:[1,5,6,8,14,25,39,40],hurri:1,hybrid:3,hyper:1,id_val:8,idea:3,ideal:[1,41],ident:[2,10,12,20,37,41],identifi:3,idl:3,ignor:40,imag:40,imagin:1,immedi:[2,3,20,25,32],immut:2,impair:1,impl:40,implement:[2,4,21,29,40,41],implic:12,implicit:[0,3,8,13,19,37,39],implicitli:[2,3,8,12,13,25,32],importantli:3,importlib:40,imposs:3,improv:[1,37],in_:10,in_queri:20,inc:39,includ:[6,8,20,35,37,40,41],include_foreign_key_constraint:30,include_rout:40,incomplet:40,increment:20,index:[1,4,10,21,30,39,41],index_on_nam:8,indic:[21,29,41],individu:[2,20,40],infer:20,info:40,inform:[2,8,12,13,19,25,41],inherit:[2,8,13,20,25,37,41],ini:[5,40],init:[5,23,37,40],init_app:[11,34,35,40],init_kwarg:23,init_pool:[23,24],initi:[2,4,9,12,20,21,23,29,31,35,40],inject:[2,12],inlin:[12,19,23,37],inner:[2,13,23],ins:12,insert:[4,12,13,20,25,37,39,41],insid:5,insist:10,inspir:[10,12],instal:[5,6,35,37,38,40],instanc:[2,3,5,8,9,10,12,13,19,20,21,23,25,28,29,31,37,40,41],instant:20,instanti:[12,20,25,29,40],instead:[1,2,3,10,12,13,20,21,25,27,37,40,41],integ:[5,8,9,10,12,21,39,41],integerproperti:[9,28],integr:[34,37,38],intend:21,intens:[1,3],interact:[40,41],interfac:[2,4,19,29,37],interfaceerror:23,interfer:37,interleav:1,intern:[2,8,10,13,20,21,32,37],internet:3,interpret:[10,32],interrupt:1,interv:23,introduc:[8,10,37],introduct:38,intuit:3,invalid:34,invent:8,invers:40,invert_get:21,invertdict:21,invok:[20,23],involv:[8,12],irrelev:19,is_:20,is_local_root:37,isdigit:34,ish:3,isol:[2,23,25,37],isolation_level:2,issu:[3,6,8,9,37,39,40],item:[10,12,29,30,40],iter:[2,8,10,12,19,20,21,23,24,25,37,39],its:[1,2,3,8,10,19,20,21,23,25,29,32,35,37,41],itself:[1,2,3,8,10,12,13,21,23,40],iuliia:37,jack:12,java:39,jeff:8,jekel:37,jetbrain:39,jim:37,job:25,join:[4,10,19,20,29,37,39],join_queri:10,join_without_n_plus_1:3,jone:[12,39],json:[4,20,23,34,37,40],json_support:[15,16,17,20],jsonb:[9,37],jsonpathtyp:[23,37],jsonproperti:[9,20,28],julio:37,just:[1,2,3,5,8,10,12,13,20,21,32,34,37,40,41],keep:[1,3,6,8,10,37,41],kei:[6,8,10,20,21,29,37,41],kentoseth:37,kept:[3,29],keyout:6,keyword:[2,8,10,20,29,41],kill:[1,3],kind:3,king:[39,40],kinwar:37,know:[1,2,3,8,13],knowledg:[10,12,20,41],known:[2,10,40,41],kooten:37,kovalev:37,kubernet:40,kwarg:[17,19,20,21,23,24,25,30,31,32,35,37],label:[29,37],lacerda:37,lambda:10,larg:[3,25],larger:[1,40],last:[1,2,3,10,19,20,32,40,41],last_nam:8,later:[1,2,8,10,12,25,35],latest:[20,25,39,40],latter:2,law:3,layer:3,layout:[37,40],lazi:[0,8,15,25,33,34,37],lazili:[2,34],lazy_engin:8,lead:[3,9],leaf:10,learn:[2,3],least:[1,3],leav:25,led:25,left:[2,10,20,39],legaci:12,len:10,length:12,lengthi:3,leosussan:39,less:[1,2,3,12],lesson:14,let:[1,2,3,10,12,13,40,41],level:[2,3,8,9,12,20,21,23,25,41],leverag:[3,9],lib:[6,40],libffi:40,librari:[37,39,40,41],licens:[14,39],liebig:3,lifetim:34,lightn:40,lightweight:[5,14],like:[1,2,3,5,6,8,9,10,12,20,21,25,27,34,35,40,41],likewis:[2,10,29,41],limit:[1,3,23,24,37,41],line:[2,5],linearli:1,link:[3,5,8,40],list:[2,6,8,9,12,25,29,40,41],listen:[3,23],liter:[1,25],littl:[1,6],live:[1,40],load:[2,3,4,10,19,20,25,29,37,39,40],load_modul:40,loader:[4,8,15,16,17,19,20,24,25,37,39],lobbi:14,local:[2,5,6,15,40],localhost:[3,6,8,10,11,12,29,34,35,40,41],locat:[20,37],lock:[3,40],log:[40,41],logger:40,logging_nam:[2,25],logic:[1,3,40],login:6,longer:[1,2,3,8,25,37],look:[1,3,6,10,20,35,40],lookup:[20,37],loop:[1,8,19,23,24,25,31,37,39],lose:25,lost:3,lot:[1,3,10],love:3,low:3,lower:[1,8,41],made:[8,10,12,37,40,41],magic:[2,10,41],magicstack:[8,39],mai:[1,2,3,8,9,10,13,19,25,34,40,41],main:[2,3,5,8,10,12,40,41],main_app:5,maintain:[3,8,21,39,40],mainten:37,major:3,make:[1,2,3,5,6,8,10,20,23,25,34,40,41],make_express:28,make_url:40,manag:[0,1,13,19,25,32,34,37,40,41],mandatori:12,mani:[2,3,4,6,8,23,24,25,37,41],manipul:37,manual:[4,8,9,10,12,20,25,32,37,40],map:[2,8,10,12,39,40,41],mapper:8,marissa:8,mark:[3,21,25],martin:37,masonri:40,mass:41,massiv:20,master:39,match:10,matter:[2,10,12],max:[2,40],max_cacheable_statement_s:23,max_cached_statement_lifetim:23,max_inactive_connection_lifetim:23,max_overflow:3,max_queri:23,max_siz:23,maximum:35,mean:[1,2,3,10,13,20,21,23,25,37,41],meaningless:[2,21],meant:[2,41],meanwhil:[20,32,37],meet:6,member:41,memori:[1,2,8,20,21,25,41],mention:[1,2,12,40,41],mess:[1,19],messag:[3,37],met:41,meta_path:27,metaclass:20,metadata:[2,8,12,19,20,21,30,37,39,40,41],method:[2,3,12,19,20,21,23,25,29,32,37,41],michael:39,middl:[1,35],middlewar:[8,35,37],might:[1,2,6],migrat:[4,15,40],mike:3,min:40,min_siz:[2,23],mind:1,minhe:39,minim:[1,12],minimum:3,misc:37,miser:3,miss:[2,37],mission:3,mistak:37,mix:3,mixin:[21,37,41],mkdir:40,mkvirtualenv:6,mock:29,mod:40,mode:[2,3,23,25,32,35,40],model:[2,3,4,5,8,9,12,19,20,21,24,25,29,34,37,38,39],model_base_class:19,model_class:[19,21],model_kei:8,modelload:[8,10,29,37],modern:3,modif:41,modifi:[10,40,41],modul:[2,5,8,15,16,37,40],modulenotfounderror:5,moment:[1,2,3],more:[1,2,3,6,8,10,12,13,19,20,21,25,37,41],morgan:37,most:[2,3,8,12,13,20,25,37,41],mostli:[3,20],move:[37,41],much:[1,2,3,9,12,13,25],multi:1,multiparam:[2,19,24,25,37],multipl:[1,2,4,10,25,29,37,41],multipleresultsfound:[25,26],multiplex:1,multitask:[0,3],musl:40,must:[1,2,3,5,8,12,19,25,29,40,41],mutabl:37,my_app:5,myapp:40,mydb:40,mydb_test:40,mydialect:23,mykyta:37,mymodel:40,myself:[1,3],mysql:[8,39,41],mytab:13,mytabl:13,myuser:20,name:[1,2,3,5,6,8,9,10,12,19,20,21,29,31,34,35,37,39,40,41],name_or_url:31,namespac:27,narrow:6,nativ:[9,37],natur:[1,3],neal:37,nearli:1,neat:3,necessari:[1,3,23,34],necessarili:2,need:[1,2,3,5,6,8,9,10,12,13,19,25,34,35,37,40,41],nest:[2,4,10,25,29,32],network:[1,2,3],never:[1,3,12,13,25,41],nevertheless:3,new_child:10,new_nam:8,new_names_dict:8,newcom:14,newli:[20,23,41],next:[1,3,5,12,23,24,25,40],nicknam:[5,12,34,40,41],no_deleg:19,non:[1,8,11,12,25,37,41],nonam:[5,12,41],none:[2,8,10,12,19,20,21,23,24,25,28,29,30,31,35,37,40,41],none_as_non:[15,20,29],noresultfound:[25,26],normal:[2,3,9,10,12,13,19,20,21,32,40,41],nosuchrowerror:26,note:[2,10,25,34,37,38,41],noth:[1,2,3,8,13,29,37],notic:[1,2,10],now:[1,2,3,5,6,8,10,12,13,14,19,25,29,37,40,41],nullabl:[9,12,40],nullpool:[11,23,37],nulltyp:23,number:[2,3,9,20,35,40],numer:24,obj:30,object:[2,3,5,8,9,10,12,19,20,21,23,24,25,28,29,30,32,37,39,40,41],objectproperti:[9,28],obviou:[1,3],obvious:[3,37],occasion:[3,19],occur:[1,23],off:[2,3,34,37],offer:[2,12,32,37],offici:[5,6,19,27],often:[19,20,21],okai:[1,40],olaf:37,old:[20,39],olexii:37,omit:[10,20],on_claus:[20,29],on_connect:23,onc:[1,2,3,5,8,10,23,25,41],one:[1,2,3,8,10,12,14,19,20,25,29,37,41],one_or_non:[2,12,19,25,37],ones:[2,3,19,29,40,41],onli:[1,2,3,5,10,11,12,13,14,19,20,21,25,29,32,35,40,41],ons:2,open:[5,6,13,25,39],openid:3,opensourc:39,openssl:[6,40],oper:[1,2,3,6,19,20,21,25,37,38,39,40],opposit:[3,25],opt:25,optim:2,option:[1,2,8,10,11,19,20,23,25,29,37,40],order:[1,2,8,10,20,25,40,41],ordinari:10,org:[5,39],origin:[6,12,20],orm:[0,2,4,10,14,39,41],orphan:2,orz:39,oss:39,other:[1,2,3,4,5,6,11,12,13,19,20,21,25,29,35,37,40,41],otherwis:[21,23,25,41],our:[1,3,5,40,41],out:[1,2,3,6,12,21,25,41],outer:[2,10,13,20],outerjoin:[8,10,29,37,39],output:[10,12],outsid:[1,41],over:[3,40],overal:[3,10],overhead:[1,3],overlap:1,overload:3,overrid:[19,20,21,37,40],overwrit:40,own:[1,2,3,8,10,11,12,40,41],owner:6,packag:[5,15,16,35,37,39,40],page:1,pai:34,pair:[10,20],parallel:[1,2],param:[19,24,25],paramet:[2,4,12,20,21,23,24,25,29,35,37,41],paramstyl:[2,24],parent:[2,8,10,12,19,32,37],parent_id:[8,10,37],parents_x_children:10,parentxchild:10,pars:[25,37],part:[1,2,3,6,8,40,41],partial:[2,8,37],particular:23,particularli:20,pascal:37,pass:[1,6,23,25,29,35,37,40],passfil:23,passin:6,passiv:37,passout:6,password:[5,6,23,35,40],patch:[8,18,37],patch_asyncio:18,patch_schema:30,path:40,pattern:[3,8,10],paus:1,pavol:37,payload:2,pem:6,pend:20,peopl:[3,10],pep:[37,39],per:[23,25],perform:[1,3,8,34],perman:[2,25,35,37],peter:39,pgcompil:23,pgdialect:23,pgexecutioncontext:23,pgjone:39,phase:40,philip:39,piec:[1,10,41],pip:[5,6,35,37,40,41],place:[21,25,37],plai:[2,3,19,25],plain:[2,8,39],plain_old_java_object:39,platform:[3,40],pleas:[1,2,6,8,9,10,12,13,19,25,34,37,40,41],plu:19,plugabl:40,pluggi:40,plugin:40,plural:41,poetri:[5,37,40,41],point:[1,3,25,27,40],poli:21,pool:[2,3,4,23,24,25,34,35,37,40],pool_class:[11,23],pool_max_s:[35,40],pool_min_s:[35,40],poolev:23,pop_bind:[2,19,37,41],popo:39,popul:[20,41],popular:[40,41],port:[3,12,23,35,40],posit:[20,23,25,29],possibl:[1,2,3,5,6,10,12,23,29,34,37,40,41],post:[2,6,10,40],postgi:39,postgr:[5,6,8,31,34,35,40],postgreserror:23,postgresql:[2,3,6,8,9,10,11,12,19,23,29,31,32,37,39,40,41],postgresqlimpl:40,postprocess:25,potenti:10,power:41,practic:[1,3,25,41],predict:1,preemptiv:1,prefer:[3,29,34,41],prefetch:37,prefix:31,prepar:[5,23,24,25,37],preparedstat:[23,24],present:[2,10,25,37],press:40,pretti:[3,12],prevent:37,previou:[1,2,10,12,19,35,37],previous:[1,2,10,40,41],primari:[20,37,41],primary_kei:[5,8,9,10,12,19,21,34,39,40,41],primarykeyconstraint:[40,41],princip:3,print:[4,9,10,12,20,39,41],prioriti:[3,40],privkei:6,pro:0,probabl:2,problem:[1,3,5,14,37],proce:23,process:[1,2,10,23,37,40,41],process_row:24,processor:[10,37],produc:[2,10],product:[0,38],profil:[9,28,37],program:[0,2,3,10,13,41],progress:2,prohibit:13,project:[5,6,8,38,41],promis:10,prop_nam:[9,21,28],propag:13,proper:3,properli:40,properti:[2,3,4,5,8,10,12,19,23,24,25,29,32,37,41],propos:6,protect:37,provid:[1,2,8,9,10,12,13,19,20,25,27,29,35,37,40],proxi:20,psql:6,psycopg2:[2,40],psycopg:40,publicli:[19,25],pull:4,pure:[8,37,41],push:6,put:[2,6,12,32],pwd:6,pycharm:39,pydant:40,pypi:[14,37],pyproject:40,pytest:[6,40],python:[1,2,3,5,6,8,9,14,18,21,27,37,38,40,41],pythongino:39,pythonpath:[5,40],qbasic:39,qualiti:3,quart:[37,39],queri:[0,3,4,6,9,10,12,15,19,20,21,23,24,25,29,35,39,41],query_executor:19,query_ext:19,query_param:8,querymodel:20,question:[3,4],queue:3,quick:[4,40],quickli:8,quit:[2,3,8,12,20,40,41],qulaz:37,rais:[2,8,13,19,25,32,37],raise_commit:[13,32],raise_for_statu:40,raise_rollback:[13,32],raiseerr:40,ran:1,randint:[8,10],random:[8,10],rang:[8,10,37],rather:[1,3,8,20,41],raw:[1,2,3,4,10,11,12,25,29,37,41],raw_conn:[23,24],raw_connect:[25,37],raw_pool:[23,24,25,37],raw_transact:[13,23,24,32],rdbm:[34,41],reach:[32,34],reaction:3,read:[1,2,8,19,20,21,25,41],read_root:3,readabl:1,readi:[1,6,40],readm:[6,37],real:1,realiti:3,realli:1,reason:[3,23],receiv:[1,8,23],recent:[2,25,41],recogn:[13,20,37],recommend:[2,19,25,34,41],reconsid:3,record:[37,41],recov:37,recurs:[10,25],recv:1,red:1,redirect:21,reduc:[10,12,40],refactor:37,refer:[2,8,10,13,14,19,25,41],referenc:[4,25,32],reflect:21,refresh:37,regardless:40,regist:1,regular:[10,12],reinvent:3,rel:5,relat:[2,8,10,39,40],relationship:[3,4,20,25,37],releas:[2,8,15,23,24,25,34,35,41],relev:[19,25,37],reli:3,reliabl:[3,40],reload:[20,28,40],remain:[8,37,40,41],rememb:[3,6,20,40,41],remind:3,remov:[2,20,37],renam:37,repeat:1,replac:[2,21,29,35,37],repli:3,repo:6,report:4,repositori:40,repr:[23,24,25],repres:[8,19,25,32,41],represent:37,reproduc:6,req:6,requeijo:37,request:[3,4,8,20,34,35,40],requir:[1,2,3,6,12,19,20,21,25,37,40,41],requirements_dev:6,reset:37,reset_loc:37,reskov:37,resourc:[1,2,3],respond:3,respons:[1,10,25,34,35],rest:[9,20,32,34,40,41],restart:40,restrict:19,result:[1,2,4,10,19,21,23,25,29,37,41],result_processor:[2,23],resum:[1,3,19],retri:40,retriev:[2,10,20,38,40],retry_interv:40,retry_limit:40,return_model:[2,19,20,24,25],reus:[0,3,10,13,25,29,34,37],reusabl:[0,25],revamp:37,revers:[2,12,25],revert:[2,37],review:[37,40,41],revis:[4,9,40],rewritten:[12,37],rewrot:37,ricardo:39,rich:[10,37],right:[0,40],risk:[2,12,41],roald:37,role:6,roll:[13,25,32],rollback:[3,13,23,24,25,32,37],roman:37,room:41,root:[3,25,40],rootdir:40,roughli:40,rout:34,router:40,row:[2,8,10,12,20,23,24,25,29,37,41],rowproxi:[2,12,29,37],rsa:6,rst:6,rule:[2,12,13,25,29],run:[1,2,3,4,5,6,9,10,25,29,34,35,40,41],run_until_complet:41,runtim:40,sa_conn:25,sacrific:3,safe:[1,12,25],sai:[1,3,34,40,41],said:[1,3],same:[1,2,3,4,5,9,10,12,13,19,20,21,25,29,34,35,37,40,41],sampl:[5,40],sanic:[11,15,33,37,39],sanicframework:39,save:[1,2,25,28,40],savepoint:[13,32],scalabl:3,scalar:[2,3,8,12,19,20,24,25,37,41],scale:1,scenario:[1,2,3,10,12,41],scene:[2,40],schedul:1,schema:[4,5,15,16,17,19,21,23,25,39,40,41],schema_ext:19,schema_for_object:25,schema_visitor:19,schemadropp:30,schemagener:30,schemaitem:[19,37],scope:[3,6,34],search:1,sec:1,second:[1,2,3,10,20,25],secret:40,section:[6,14],secur:[8,40],see:[1,2,5,6,8,12,13,25,40,41],seek:3,seem:[1,12],select:[2,3,8,9,10,12,13,19,20,25,29,37,39,41],select_from:[8,10,29],self:[4,8,9,19,20,23,25,29,34],send:[3,6,23],sens:3,sent:19,sep:10,separ:[3,6,10,13,37,40],sequenc:[2,23,30],sequence_nam:23,sequenti:1,sergei:37,seri:[2,25],serv:3,server:[2,3,6,8,25,34,35,37,38,41],server_default:[8,9,10],server_set:23,servic:[39,40],session:[3,40],sessionmak:3,set:[2,4,6,8,10,12,19,20,21,23,25,29,34,35,37,40,41],set_bind:[2,8,19,25,37,41],set_except:8,set_isolation_level:23,set_main_opt:40,set_result:8,setattr:[10,29,37],setter:[8,10,19],settl:34,setup:[5,6,23,35],sever:[1,2,19,20,25,41],shall:[13,20,34,40],share:[1,2,25,34,35,37],sharp:1,shini:40,shortcut:[2,3,10,13,17,19,20,25,29,40,41],shortest:3,should:[1,2,3,6,10,11,19,20,21,23,25,27,35,37,40,41],shouldn:[3,37],show:[10,40],shown:[2,32],shtrikker:37,shut:40,shutdown:40,side:[2,3,10,25],sign:1,silenc:1,simeon:37,similar:[1,2,5,6,10,12,13,19,20,37,40,41],similarli:[2,10,12,13,20,25,37],simpl:[1,2,3,5,8,12,19,37,38,39,41],simpler:[1,2,41],simplest:29,simpli:[1,2,3,8,9,10,19,20,25,29,34,35,40,41],simplic:1,simplifi:40,simul:[2,8],sinc:[1,20,37],singl:[1,2,3,10,12,20,23,25,41],singular:41,situat:[3,25,32],size:[1,40],skip:[13,32,40],sleep:[1,2,3],slice:1,slower:[3,41],small:[1,2],smaller:3,smarter:3,softwar:[14,39],sole:23,solut:[3,27,41],solv:[1,3,14],some:[1,2,3,5,6,8,9,10,12,20,25,35,37,40,41],someth:[1,2,3,10,40],sometim:[2,3,34,41],soon:34,sourc:[14,20,25,37,39,40],speak:[2,41],special:[10,23,34],specif:[2,14,20],specifi:[2,9,10,12,13,20,21,23,25,29,35,37,41],split:[1,41],sql:[2,3,4,9,10,12,19,20,23,25,30,37,39,41],sqlalchemi:[2,3,4,5,9,10,12,14,17,19,20,21,23,25,29,30,31,35,37,39,40,41],sqltype:23,src:[37,39,40],ssl:[4,6,23,35,37,40],ssl_cert_fil:6,ssl_key_fil:6,stabil:8,stabl:[37,39,41],stack:[2,25,37,40,41],standard:[2,3],stare:40,starleet:37,starlett:[8,15,27,33,37,39,40],start:[1,2,3,4,8,13,14,20,25,32,34,37,38,41],startup:40,starv:0,starvat:3,state:[1,3,41],stateless:[3,8],statement:[2,3,8,12,13,20,23,24,37,41],statement_cache_s:23,statement_compil:23,statu:[2,12,13,19,20,24,25,37,41],status_cod:40,stave:3,step:[1,2,3,6,14,40],stereotyp:3,stick:1,still:[1,2,3,8,10,12,21,25,29,32,37,40,41],stop:[3,13,34],storag:[21,37],store:[1,2,9,10,21,40],stori:0,storm:37,str:[3,9,29,40],straightforward:3,strang:41,strategi:[2,15,16,17,29],string:[2,8,9,10,12,19,20,21,23,25,34,37,39,41],stringproperti:[9,28],strongli:3,structur:[5,10,20,25],stub:39,stuff:41,style:[3,10,12,37],sub:[3,8,21,29,37,40],subclass:[2,12,19,21,29,32,37,41],subj:6,subload:10,submit:[4,40],submodul:[15,16],subpackag:[15,16],subqueri:[20,37],subset:6,succeed:25,success:5,successfulli:[32,41],sugar:3,suggest:[1,41],suit:[20,37],summari:3,support:[2,4,6,9,10,14,15,18,19,20,25,29,33,37,39,40,41],supports_native_decim:23,suppos:[2,21,29,32],sure:[1,3,6,10,25,34,40],svx:6,swagger:40,sweet:1,symbol:19,sync:8,synchron:[1,19],syntax:6,sys:27,system:[1,2,6,8,10,40],tabl:[4,5,10,12,13,19,20,21,23,30,37,40,41],table_nam:23,tableclaus:12,tag:6,take:[1,2,3,8,11,12,20,25,29],taken:[1,2,25,34],talk:3,target:[40,41],target_metadata:[5,8,40],task:[1,2,3,15,25,35],tast:[12,40],team:[1,20,37],team_id:20,technic:20,telegram:39,tell:[2,41],ten:[1,3],termin:[6,41],test:[6,37,38],test_aiohttp:6,test_crud:40,test_gino:6,test_us:40,testclient:40,text:[6,8,9,10,12,19,29,37],textual:[2,10],than:[1,2,3,8,10,11,13,20,21,25,39,41],thank:[1,2,37],thei:[1,2,3,6,8,10,12,13,20,21,25,37,40,41],them:[1,3,5,8,9,10,20,37,40,41],themselv:1,theoret:[3,37],therefor:[1,2,3,8,12,19,20,25,34,41],thi:[1,2,3,5,6,7,8,9,10,12,13,18,19,20,21,23,25,27,29,31,32,34,35,36,37,40,41],thing:[1,2,3,5,12,21,37],think:[1,3,8],third:[10,12],those:[3,25],though:[1,3,12,20,34,41],thought:40,thousand:[1,3],thread:[1,2,3],three:2,through:[2,4,6,10,13,19,27,37,40,41],throughput:[1,3],thu:[1,2,3,12,13,25,37],tiago:37,tiangolo:39,tim:39,time:[1,2,3,5,8,9,10,13,19,20,25,34,40],timeout:[19,20,23,24,25,37],timeouterror:25,timestamp:9,timezon:21,tini:3,tip:4,titl:[39,40],to_dict:[20,37,40],togeth:[1,2,25,32,40,41],token:3,toml:40,toni:[37,39],too:[1,2,3,8,12,13,25,40,41],tool:[3,5,39,40,41],toolkit:5,top:[2,3,8,12,14,25,40,41],tornado:[15,33,37,39],tortois:39,total:[1,3],touch:[13,32],touchabl:2,tox:6,track:[40,41],trackedmixin:21,tradit:[8,12,41],transact:[2,3,4,8,10,15,16,17,19,23,24,25,34,39,40],transform:10,translat:[2,9,10],trap:[13,32],travers:2,traverse_singl:30,treat:[2,10,21,25,31],tree:10,tri:[1,3,25,37,41],trigger:[1,3,21],trio:8,troubleshoot:6,truncat:41,tupl:[2,8,10,20,25,29],tupleload:[8,10,29,37],turn:[1,2,10,25,34,37],tutori:[12,14,39,40,41],twice:[2,4,23,41],twist:[3,39],two:[1,2,3,8,10,12,13,20,25,32,40,41],tx1:[13,32],tx2:[13,32],tx3:32,txt:6,type:[2,4,8,9,10,12,20,21,25,29,32,37,41],type_nam:23,typic:[3,8],ua1:10,ua2:10,ubuntu:39,uid:[10,40],ultim:3,ultra:39,unbind:19,unchang:41,under:[2,8,10,12,19,20,21,29,37,40,41],underli:[2,12,13,25,32,37],unfortun:3,unicod:[5,8,10,12,21,23,34,40,41],unifi:[2,37],uninitializederror:[8,26,37],uniqu:[2,10,41],unique_constraint:21,unique_id:21,uniqueconstraint:21,unix:39,unknown:[10,37],unknownjsonpropertyerror:26,unless:[2,3,20,41],unlik:1,unnam:[37,40],unnecessarili:34,unpin:37,unpredict:[1,3,34],unrecogn:35,unreli:3,unreus:2,unset:2,unspecifi:20,untest:2,until:[1,8,9,25],untouch:41,unus:[2,3],unwant:12,unwrap:23,updat:[2,4,6,9,20,21,25,29,32,37,38,39,40],update_execution_opt:[25,37],updaterequest:[20,41],upgrad:[2,5,8,37,40],upon:[13,23,32],upstream:8,urh:1,url:[2,5,19,23,24,31,35,37,40,41],usabl:[2,25,37],usag:[2,4,5,20,25,35,37],use:[1,2,3,4,5,6,9,10,11,12,13,19,20,23,25,27,29,32,34,35,37,39,40,41],use_connection_for_request:[35,40],used:[1,2,8,10,11,12,19,20,21,23,25,29,37,40,41],useful:[2,3,12,20,25,29,32],user1:20,user2:20,user:[2,3,4,5,6,9,10,11,12,19,20,21,23,25,29,32,34,35,39,40,41],user_id:[8,10,12,20,34],usermodel:40,usernam:[5,40],users_t:2,uses:[8,10,20,31,40],using:[1,2,3,6,8,10,12,13,20,21,23,25,29,37,40,41],usual:[1,2,3,8,9,10,12,19,20,21,25,40,41],utc:10,util:[21,27],uuid4:40,uuid:40,uvicorn:[39,40],uvicornwork:40,uvloop:[1,39],v7oze:25,val:[8,9,28],valid:12,valu:[2,8,10,12,13,19,20,21,23,25,28,29,34,37,40,41],valueload:29,van:37,vanilla:[2,12],vargovcik:37,vari:40,variabl:[5,39,40,41],variant:2,venv:40,veri:[1,2,3,9,10,12,20,23,25,32,34],version:[5,6,8,17,18,20,35,37,40],view:40,virtual:6,virtualenv:40,virtualenvwrapp:6,visit:[8,10,19],visit_foreign_key_constraint:30,visit_index:30,visit_metadata:30,visit_sequ:30,visit_t:30,visual:39,vladimir:37,volkova:37,volunt:6,wai:[2,3,6,8,9,10,12,13,19,25,40,41],wait:[1,3,9,13,20,25,40],wang:[37,39],wanna:[1,3],want:[1,2,3,5,6,12,19,20,25,34,40,41],ware:8,warn:[37,40],wast:[1,2,3],watch:[1,39],weakref:37,web:[1,6,8,37,39],websit:6,welcom:[6,12,37,39],well:[1,2,3,21],were:[1,3],what:[1,2,3,4,12,13,19,40],whatev:[2,3,10,20,25],wheel:3,when:[0,1,2,5,6,8,10,12,13,18,19,20,21,25,29,32,34,37,40,41],whenev:40,where:[2,3,8,9,10,12,19,20,25,29,32,40,41],wherea:1,whether:[6,25,32,37],which:[1,2,3,8,10,12,19,20,21,23,25,32,34,37,40,41],whichev:37,whoever:6,whole:[1,37,41],whose:[12,32,41],why:[0,1,10,41],wide:23,wider:41,wiki:39,wikipedia:39,wip:[7,34,36],wise:3,wish:[2,6,10,25],with_bind:[2,8,10,12,19,37],within:[1,3,13,23,25,34,37],without:[1,2,3,9,12,13,19,25,37],won:[1,2,3,8,12,13,32,37,40,41],wonder:1,word:41,work:[1,2,3,4,5,6,10,12,15,20,21,25,33,37,40,41],workaround:8,workdir:40,worker:40,world:[1,12,34],worri:[2,3,8,13,37],worth:[2,12],would:[1,3,6,8,10,19,20,29],wrap:[8,21],wrapper:[2,3,8,21,25,39],write:[1,2,3,4,8,10,38,41],written:[12,39],wrong:[1,3,37],wrote:[1,3],ww4ronfhiqi:39,www:39,x509:6,xss:40,xxx:[20,39],xxxx:4,yai:[1,8],yellow:1,yes:1,yet:[1,8,9,25],yield:[1,3,12,20,21,25,29,40],yml:40,you:[1,2,3,5,6,8,9,10,12,13,19,20,21,25,32,34,35,37,40,41],your:[1,2,3,5,6,8,10,12,13,19,25,37,40,41],your_name_her:6,yourdbnam:40,youtub:39,yurii:37,zen:39,zenof:39,zero:[20,25],zone:[9,10]},titles:["Explanation","Asynchronous Programming 101","Engine and Connection","Why Asynchronous ORM?","How-to Guides","Use Alembic","Contributing","CRUD","Frequently Asked Questions","JSON Property","Loaders and Relationship","Connection Pool","Schema Declaration","Transaction","Welcome to GINO\u2019s documentation!","Reference","API Reference","gino package","gino.aiocontextvars module","gino.api module","gino.crud module","gino.declarative module","gino.dialects package","gino.dialects.asyncpg module","gino.dialects.base module","gino.engine module","gino.exceptions module","gino.ext package","gino.json_support module","gino.loader module","gino.schema module","gino.strategies module","gino.transaction module","Extensions","Sanic Support","Starlette Support","Tornado Support","History","Tutorials","\u5b98\u5ba3\uff1aPython \u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668","Build a FastAPI Server","GINO Basics"],titleterms:{"\u4f18\u52bf\u4e0e\u4e0d\u8db3":39,"\u5148\u8bf4":39,"\u5173\u4e8e\u4f5c\u8005":39,"\u518d\u8bf4":39,"\u53c2\u8003\u6587\u732e":39,"\u5b98\u5ba3":39,"\u5efa\u8bbe\u793e\u4f1a\u4e3b\u4e49":39,"\u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668":39,"\u65b9\u4fbf\u5feb\u6377":39,"\u662f\u8c01":39,"\u7b80\u5355\u660e\u4e86":39,"new":40,One:10,The:1,Use:5,Useful:14,access:3,add:40,admin:8,advanc:10,aiocontextvar:[8,18],alemb:[5,8,40],api:[16,19,37,40],appli:5,ask:8,asynchron:[1,3],asyncio:[3,8],asyncpg:23,base:24,basic:[13,41],batch:8,bug:6,build:40,bulk:8,can:8,column:8,complex:8,con:1,configur:35,connect:[2,8,11,35,41],content:[17,22,27],contextvar:37,contribut:6,control:13,cooper:1,core:12,creat:[2,5,9,40,41],crud:[7,20,41],current_connect:2,databas:[3,8],declar:[12,21,41],defin:8,delet:41,depend:40,develop:37,dialect:[22,23,24],differ:8,django:8,document:[6,14],doe:8,don:3,done:3,earli:37,engin:[2,8,12,25,37],except:26,execut:[2,8],exist:8,explan:0,explicit:3,express:10,ext:27,extens:[33,40],fastapi:40,featur:[6,8],feedback:6,first:5,fix:6,fly:8,frequent:8,get:[6,41],gino:[8,12,14,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,37,39,40,41],ginoconnect:37,guid:4,guidelin:6,help:3,histori:37,hook:9,how:[3,4,8],implement:6,implicit:2,index:[8,9],initi:8,insert:8,instal:41,integr:40,interfac:8,introduct:41,join:8,json:9,json_support:28,lazi:[2,35],link:14,load:8,loader:[10,29],local:37,manag:2,mani:10,manual:13,migrat:[5,37],model:[10,40,41],modul:[17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],multipl:8,multitask:1,nest:13,none_as_non:37,note:40,oper:41,orm:[3,8,12],other:10,packag:[17,22,27],paramet:8,pool:11,print:8,pro:1,product:[3,40],program:1,project:40,properti:9,pull:6,python:39,queri:[2,8,37],question:8,quick:9,raw:8,refer:[15,16],referenc:10,relationship:[8,10],releas:37,report:6,request:6,result:8,retriev:41,reus:2,reusabl:2,revis:5,right:3,run:8,same:8,sanic:34,schema:[12,30],self:10,server:40,set:5,simpl:40,sql:8,sqlalchemi:8,ssl:8,starlett:35,start:[6,9,40],starv:3,stori:1,strategi:31,submit:6,submodul:[17,22],subpackag:17,support:[8,34,35,36],tabl:8,task:37,test:40,through:8,tip:6,tornado:36,transact:[13,32,37],tutori:38,twice:8,type:6,updat:[8,41],usag:[10,13],use:8,user:8,welcom:14,what:8,when:3,why:3,work:[8,34,35],write:[6,40],xxxx:8}}) \ No newline at end of file diff --git a/docs/en/1.0/tutorials.html b/docs/en/1.0/tutorials.html new file mode 100644 index 0000000..7212625 --- /dev/null +++ b/docs/en/1.0/tutorials.html @@ -0,0 +1,232 @@ + + + + + + + + Tutorials - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/tutorials/announcement.html b/docs/en/1.0/tutorials/announcement.html new file mode 100644 index 0000000..e91da26 --- /dev/null +++ b/docs/en/1.0/tutorials/announcement.html @@ -0,0 +1,488 @@ + + + + + + + + 官宣:Python 异步编程再添一利器 - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    官宣:Python 异步编程再添一利器

    +
    +

    GINO 填补了国内外 asyncio ORM 领域的空白

    +
    +

    随着 Tornado1 和 asyncio2 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +GINO 了解一下!

    +../_images/python-gino.webp +
    +

    GINO 是谁

    +

    GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义3手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,你肯定要问一句“这是谁”。

    +

    ORM,即关系对象映射(Object-Relational Mapping4),是一类开发人员喜闻乐见的效率工具,它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢?

    +

    因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 current_user.name +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试?

    +

    传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,但应用到大型项目中却十分考验开发人员的平均水平。

    +

    所有这些问题如果再放进异步编程的环境里,那就是 O(n2) 的复杂度了——哦不,是 +O(2n)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。

    +

    所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,我索性在 1.0 稳定版发布的前夕做个年终总结。

    +
    +
    +

    先说“方便快捷”

    +
    +

    “方便快捷”主要说的是开发效率。

    +
    +

    重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。

    +
    from gino import Gino
    +db = Gino()
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +
    +

    这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟?

    +

    没有错,这就是 SQLAlchemy ORM5 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core6(SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic7、各种 SQLAlchemy 的增强插件8、专业领域的 PostGIS/geoalchemy9等,GINO 全都兼容。

    +

    是不是十分方便、十分快捷?不止这样。

    +

    GINO 一站式地解决了常用 CRUD 快捷方式10、上下文管理(aiocontextvars1112)、 +数据库事务封装和嵌套13、连接池管理和懒加载14等多项便捷功能,无额外依赖关系,即装即用。

    +
    daisy = await User.create(name="daisy")
    +await daisy.update(name="Daisy").apply()
    +
    +
    +

    GINO 还提供了15各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado1、aiohttp16、Sanic17、FastAPI18/Starlette19、Quart20什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。

    +

    为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa21 的降维打击:

    +
      +
    1. 最少侵入型22:SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。

    2. +
    3. 终身不婚型23:天生厌恶“对象”,只愿定义“表”,空手接 SQL。

    4. +
    5. 火力全开型24:最大程度的便利,非典型异步 ORM。

    6. +
    +

    最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、一秒可读百万行的 asyncpg25,以及 uvloop26(可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,深受俄罗斯和乌克兰人民的爱戴。

    +
    +
    +

    再说“简单明了”

    +
    +

    Explicit is better than implicit.

    +

    Simple is better than complex.

    +

    –The Zen of Python, PEP 2027

    +
    +

    Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,因此 GINO 的很多设计都受到了明确性的影响。

    +

    比如说,GINO 的 Model 是完全无状态的普通 Python 对象(POPO28—— 例如前面的 User +类,它的实例 daisy 就是内存里面的常规对象,你可以用 daisy.name 访问属性,也可以用 +daisy.name = "DAISY" 来修改属性,或者用 u = User() 来创建新的实例,这些操作都不会访问数据库,绝对绿色环保无毒副作用。

    +

    等到需要操作数据库的时候,你一定会有感知的。比如执行 INSERT 要用 +u = await User.create(),而 UPDATE 则是 +await u.update(name="Daisy").apply()

    +
    +

    Hint

    +

    其中,u.update(name="Daisy") 与 u.name = "Daisy" 类似,都是只在内存里修改对象的属性,不同的是 u.update() 还会返回一个包含本次变更的中间结果,对其执行 await xxx.apply() 则会将这些变更应用到数据库里。

    +
    +

    这里的 await 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 await +就没有数据库操作。

    +

    另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders29。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 u = await User.get(1) 可以直接获取到 ID 为 1 的用户对象。但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。加载器的用法也是很简单的,比如一个用户可能写了很多本书:

    +
    class Book(db.Model):
    +    __tablename__ = "books"
    +    id = db.Column(db.Integer, primary_key=True)
    +    title = db.Column(db.String)
    +    author_id = db.ForeignKey("users.id")
    +
    +
    +

    然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者:

    +
    query = Book.outerjoin(User).select()
    +loader = Book.load(author=User)
    +async for book in query.gino.load(loader).iterate():
    +    print(book.title, "written by", book.author.name)
    +
    +
    +

    很简单的一个外连接查询 Book.outerjoin(User),配合一个直观的加载器 +Book.load(author=User),就实现了:

    +
      +
    1. 执行 SELECT * FROM books LEFT JOIN users ON ...

    2. +
    3. 将数据库返回结果的每一行中,属于 books 的字段加载成一个 Book 实例;

    4. +
    5. 然后将该行中剩下的属于 users 的字段加载成一个 User 实例;

    6. +
    7. 最后将 User 实例设置到 Book 实例的 author 属性上。

    8. +
    +

    既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。

    +
    +
    +

    优势与不足

    +

    随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM30、ORM31(是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。

    +

    GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,可以谨慎地用于生产环境了。

    +

    以下是近来统计到的关于 GINO 的应用案例:

    + +

    另外,GINO 还贴心地提供了中文文档32,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!):

    +../_images/docs.webp +

    GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。

    +
    +
    +

    建设社会主义

    +

    GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 4888 元的 PyCharm +专业版全家桶 License 一枚33(没有发票)。

    +

    目前急需帮助的有:

    +
      +
    1. 各个 Web 框架插件的维护工作需要多人认领;

    2. +
    3. 更多的例子和文档,以及中文、俄文的翻译;

    4. +
    5. MySQL 的支持。

    6. +
    +

    以及下面这些一直需要的帮助:

    +
      +
    1. 用 GINO,找 bug,提建议;

    2. +
    3. 修 bug,做功能,提 PR;

    4. +
    5. 维护社区,回答问题,参与讨论;

    6. +
    7. 最后也是最重要的:去 GitHub 上给 GINO 加一颗星星!

    8. +
    +../_images/happy-hacking.png +
    +
    +

    关于作者

    +

    87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P

    +

    特别感谢 Tony Wang 对 GINO 项目的贡献,以及所有人的贡献。

    +
    +
    +

    参考文献

    +
    +
    1(1,2)
    +

    Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado.

    +
    +
    2
    +

    Python Software Foundation. asyncio — 异步 I/O. +https://docs.python.org/zh-cn/3/library/asyncio.html.

    +
    +
    3
    +

    维基百科. GNU. https://zh.wikipedia.org/wiki/GNU.

    +
    +
    4
    +

    维基百科. 对象关系映射. +https://zh.wikipedia.org/wiki/对象关系映射.

    +
    +
    5
    +

    维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy.

    +
    +
    6
    +

    Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. +https://docs.sqlalchemy.org/en/13/core/index.html.

    +
    +
    7
    +

    Michael Bayer. Welcome to Alembic’s documentation!. +https://alembic.sqlalchemy.org/en/latest/.

    +
    +
    8
    +

    Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. +https://github.com/dahlia/awesome-sqlalchemy.

    +
    +
    9
    +

    Ricardo GarciaSilva. Does it have postgis support?. +https://github.com/python-gino/gino/issues/627.

    +
    +
    10
    +

    Fantix King. GINO 基础教程 - 增删改查. +https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations.

    +
    +
    11
    +

    Python Software Foundation. contextvars —Context Variables. +https://docs.python.org/3/library/contextvars.html.

    +
    +
    12
    +

    Fantix King. gino/aiocontextvars.py. +https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py.

    +
    +
    13
    +

    Fantix King. 数据库事务. +https://python-gino.org/docs/zh/master/how-to/transaction.html.

    +
    +
    14
    +

    Fantix King. 引擎与连接. +https://python-gino.org/docs/zh/master/explanation/engine.html.

    +
    +
    15
    +

    GINO Community. GINO Community. https://github.com/python-gino/.

    +
    +
    16
    +

    aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/.

    +
    +
    17
    +

    Sanic Community. SanicFramework. https://sanicframework.org/.

    +
    +
    18
    +

    Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/.

    +
    +
    19
    +

    Encode OSS. Starlette. https://www.starlette.io/.

    +
    +
    20
    +

    Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/.

    +
    +
    21
    +

    Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. +https://github.com/CanopyTax/asyncpgsa.

    +
    +
    22
    +

    Fantix King. 表结构定义 -GINO 引擎. +https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine.

    +
    +
    23
    +

    Fantix King. 表结构定义 - GINO core. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core.

    +
    +
    24
    +

    Fantix King. 表结构定义 - GINO ORM. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm.

    +
    +
    25
    +

    MagicStack Inc. +asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. +https://github.com/MagicStack/asyncpg.

    +
    +
    26
    +

    MagicStack Inc. uvloop -Ultra fast asyncio event loop. +https://github.com/MagicStack/uvloop.

    +
    +
    27
    +

    Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/.

    +
    +
    28
    +

    Wikipedia. Plain old Java object. +https://en.wikipedia.org/wiki/Plain_old_Java_object.

    +
    +
    29
    +

    Fantix King. 加载器与关系. +https://python-gino.org/docs/zh/master/how-to/loaders.html.

    +
    +
    30
    +

    Andrey Bondar. Tortoise ORM. https://tortoise.github.io/.

    +
    +
    31
    +

    Encode OSS. ORM - An async ORM. https://github.com/encode/orm.

    +
    +
    32
    +

    Fantix King. 欢迎来到 GINO 的文档!. +https://python-gino.org/docs/zh/master/index.html.

    +
    +
    33
    +

    JetBrains s.r.o. Open Source Licenses. +https://www.jetbrains.com/community/opensource/#support.

    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/tutorials/fastapi.html b/docs/en/1.0/tutorials/fastapi.html new file mode 100644 index 0000000..205c34d --- /dev/null +++ b/docs/en/1.0/tutorials/fastapi.html @@ -0,0 +1,734 @@ + + + + + + + + Build a FastAPI Server - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Build a FastAPI Server

    +

    In this tutorial, we’ll build a production-ready FastAPI server together. +The full functional example is available here.

    +

    Our application stack will look like this:

    +../_images/gino-fastapi.svg
    +

    Start a New Project

    +

    Instead of pip, let’s use the shiny Poetry to manage our project. Follow the link to +install Poetry, and create our new +project in an empty directory:

    +
    $ mkdir gino-fastapi-demo
    +$ cd gino-fastapi-demo
    +$ git init
    +$ poetry init
    +
    +
    +

    Then follow the Poetry guide to finish the initialization - you may say “no” to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains gino-fastapi-demo.

    +
    +
    +

    Add Dependencies

    +

    FastAPI is built on top of the Starlette framework, so we shall use the GINO +extension for Starlette. Simply run:

    +
    $ poetry add gino[starlette]
    +
    +
    +

    Then let’s add FastAPI, together with the lightning-fast ASGI server Uvicorn, and +Gunicorn as a production application server:

    +
    $ poetry add fastapi uvicorn gunicorn
    +
    +
    +

    For database migration, we’ll use Alembic. Because it uses normal DB-API, we need +psycopg here too:

    +
    $ poetry add alembic psycopg2
    +
    +
    +

    At last, let’s add pytest in the development environment for testing. We also want to +add the requests library to use the Starlette TestClient:

    +
    $ poetry add -D pytest requests
    +
    +
    +
    +

    Hint

    +

    With the steps above, Poetry will automatically create a virtualenv for you +behind the scene, and all the dependencies are installed there. We will assume +using this for the rest of the tutorial. But you’re free to create your own +virtualenv, and Poetry will honor it when it’s activated.

    +
    +

    That’s all, this is my pyproject.toml created by Poetry, yours should look similar:

    +
    [tool.poetry]
    +name = "gino-fastapi-demo"
    +version = "0.1.0"
    +description = ""
    +authors = ["Fantix King <fantix.king@gmail.com>"]
    +
    +[tool.poetry.dependencies]
    +python = "^3.8"
    +gino = {version = "^1.0", extras = ["starlette"]}
    +fastapi = "^0.54.1"
    +uvicorn = "^0.11.3"
    +gunicorn = "^20.0.4"
    +alembic = "^1.4.2"
    +psycopg2 = "^2.8.5"
    +
    +[tool.poetry.dev-dependencies]
    +pytest = "^5.4.1"
    +requests = "^2.23.0"
    +
    +[build-system]
    +requires = ["poetry>=0.12"]
    +build-backend = "poetry.masonry.api"
    +
    +
    +../_images/gino-fastapi-poetry.svg

    And there’s also an auto-generated poetry.lock file with the frozen versions. The +directory layout should look like the diagram on the right. Now let’s add the two files +to the Git repository (we will skip showing these git operations in future steps):

    +
    $ git add pyproject.toml poetry.lock
    +$ git commit -m 'add project dependencies'
    +
    +
    +
    +
    +

    Write a Simple Server

    +

    Now let’s write some Python code.

    +

    We’ll create an extra src directory to include all the Python files, as demonstrated +in the diagram below. This is known as the “src layout” providing a cleaner hierarchy.

    +../_images/gino-fastapi-src.svg

    The root Python package of our project is named as gino_fastapi_demo, under which we +will create two Python modules:

    +
      +
    • asgi as the ASGI entry point - we’ll feed it to the ASGI server

    • +
    • main to initialize our server

    • +
    +

    Here’s main.py:

    +
    from fastapi import FastAPI
    +
    +def get_app():
    +    app = FastAPI(title="GINO FastAPI Demo")
    +    return app
    +
    +
    +

    And we’ll simply instantiate our application in asgi.py:

    +
    from .main import get_app
    +
    +app = get_app()
    +
    +
    +

    Then run poetry install to link our Python package into the PYTHONPATH in +development mode. We’ll be able to start a Uvicorn development server after that:

    +
    $ poetry install
    +Installing dependencies from lock file
    +
    +No dependencies to install or update
    +
    +  - Installing gino-fastapi-demo (0.1.0)
    +
    +$ poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    +INFO:     Started reloader process [53010]
    +INFO:     Started server process [53015]
    +INFO:     Waiting for application startup.
    +INFO:     Application startup complete.
    +
    +
    +

    The --reload option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server.

    +
    +

    Hint

    +

    As mentioned previously, if you’re in your own virtualenv, the command poetry run +uvicorn can be simplified as just uvicorn.

    +

    poetry run is a convenient shortcut to run the following command in the +virtualenv managed by Poetry.

    +
    +
    +
    +

    Add GINO Extension

    +../_images/gino-fastapi-config.svg

    Now let’s add GINO to our server.

    +

    First of all, we need a way to configure the database. In this tutorial, we’ll use the +configuration system from Starlette. +Add src/gino_fastapi_demo/config.py as follows:

    +
    from sqlalchemy.engine.url import URL, make_url
    +from starlette.config import Config
    +from starlette.datastructures import Secret
    +
    +config = Config(".env")
    +
    +DB_DRIVER = config("DB_DRIVER", default="postgresql")
    +DB_HOST = config("DB_HOST", default=None)
    +DB_PORT = config("DB_PORT", cast=int, default=None)
    +DB_USER = config("DB_USER", default=None)
    +DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    +DB_DATABASE = config("DB_DATABASE", default=None)
    +DB_DSN = config(
    +    "DB_DSN",
    +    cast=make_url,
    +    default=URL(
    +        drivername=DB_DRIVER,
    +        username=DB_USER,
    +        password=DB_PASSWORD,
    +        host=DB_HOST,
    +        port=DB_PORT,
    +        database=DB_DATABASE,
    +    ),
    +)
    +DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1)
    +DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16)
    +DB_ECHO = config("DB_ECHO", cast=bool, default=False)
    +DB_SSL = config("DB_SSL", default=None)
    +DB_USE_CONNECTION_FOR_REQUEST = config(
    +    "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True
    +)
    +DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1)
    +DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1)
    +
    +
    +

    This config file will load from environment variable first, if not found then from a +file named .env from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this:

    +
    $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +
    +
    +../_images/gino-fastapi-env.svg

    Or set them in the file .env (this file must not be committed into Git, remember to +add it to .gitignore):

    +
    DB_HOST=localhost
    +DB_USER=postgres
    +
    +
    +

    Now it’s time to create a PostgreSQL database and set the connection variables +correctly here. This is usually something like createdb yourdbname, but it may vary +across different platforms, so we won’t cover this part in this tutorial.

    +
    +

    Tip

    +

    Alternatively, you could also set DB_DSN to for example +postgresql://user:password@localhost:5432/dbname to override the other individual +config values like DB_HOST defined before DB_DSN.

    +

    If defined, DB_DSN always have the higher priority over the individual ones, +regardless of where they are defined - even if DB_HOST is defined in environment +variable and DB_DSN is defined in .env file, DB_HOST is still ignored. +Default value doesn’t count.

    +
    +../_images/gino-fastapi-models.svg

    Then, create a new Python sub-package gino_fastapi_demo.models to encapsulate +database-related code, and add the code below to +src/gino_fastapi_demo/models/__init__.py:

    +
    from gino.ext.starlette import Gino
    +
    +from .. import config
    +
    +db = Gino(
    +    dsn=config.DB_DSN,
    +    pool_min_size=config.DB_POOL_MIN_SIZE,
    +    pool_max_size=config.DB_POOL_MAX_SIZE,
    +    echo=config.DB_ECHO,
    +    ssl=config.DB_SSL,
    +    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    +    retry_limit=config.DB_RETRY_LIMIT,
    +    retry_interval=config.DB_RETRY_INTERVAL,
    +)
    +
    +
    +

    At last, modify src/gino_fastapi_demo/main.py to install the GINO extension:

    +
     from fastapi import FastAPI
    ++
    ++from .models import db
    +
    + def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    ++    db.init_app(app)
    +     return app
    +
    +
    +

    Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database:

    +
    WARNING:  Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading...
    +INFO:     Shutting down
    +INFO:     Waiting for application shutdown.
    +INFO:     Application shutdown complete.
    +INFO:     Finished server process [63562]
    +INFO:     Started server process [63563]
    +INFO:     Waiting for application startup.
    +INFO:     Connecting to the database: postgresql://fantix:***@localhost
    +INFO:     Database connection pool created: <asyncpg.pool.Pool max=16 min=1 cur=1 use=0>
    +INFO:     Application startup complete.
    +
    +
    +
    +
    +

    Create Models and API

    +../_images/gino-fastapi-models-users.svg

    It’s time to implement the API now. Let’s say we are building a user management service, +through which we could add users, list users and delete users.

    +

    First of all, we need a database table users to store the data, mapped to a GINO +model named User. We shall add the model in gino_fastapi_demo.models.users:

    +
    from . import db
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default="unnamed")
    +
    +
    +../_images/gino-fastapi-views.svg

    The model definition is simple enough to explain itself.

    +

    Then we only have to use it properly in the API implementation, for which we’ll create a +new Python sub-package gino_fastapi_demo.views, and a new module +gino_fastapi_demo.views.users as follows:

    +
    from fastapi import APIRouter
    +from pydantic import BaseModel
    +
    +from ..models.users import User
    +
    +router = APIRouter()
    +
    +@router.get("/users/{uid}")
    +async def get_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    return user.to_dict()
    +
    +class UserModel(BaseModel):
    +    name: str
    +
    +@router.post("/users")
    +async def add_user(user: UserModel):
    +    rv = await User.create(nickname=user.name)
    +    return rv.to_dict()
    +
    +@router.delete("/users/{uid}")
    +async def delete_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    await user.delete()
    +    return dict(id=uid)
    +
    +def init_app(app):
    +    app.include_router(router)
    +
    +
    +

    The APIRouter holds our new APIs locally, and init_app is used to integrate it +into our FastAPI application. Here we want some inversion of control: let’s make the +APIs plugable, so that we don’t have to import all possible future views manually. We +shall use the Entry Points feature to load the dependencies. Add this code below to +gino_fastapi_demo.main:

    +
    import logging
    +from importlib.metadata import entry_points
    +
    +logger = logging.getLogger(__name__)
    +
    +def load_modules(app=None):
    +    for ep in entry_points()["gino_fastapi_demo.modules"]:
    +        logger.info("Loading module: %s", ep.name)
    +        mod = ep.load()
    +        if app:
    +            init_app = getattr(mod, "init_app", None)
    +            if init_app:
    +                init_app(app)
    +
    +
    +
    +

    Hint

    +

    If you’re running Python < 3.8, you’ll need this importlib-metadata backport.

    +
    +

    And call it in our application factory:

    +
     def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    +     db.init_app(app)
    ++    load_modules(app)
    +     return app
    +
    +
    +

    Finally, define the entry points in pyproject.toml following the Poetry document +for plugins:

    +
    [tool.poetry.plugins."gino_fastapi_demo.modules"]
    +"users" = "gino_fastapi_demo.views.users"
    +
    +
    +

    Run poetry install again to activate the entry points - you may need to restart the +Uvicorn development server manually, as the reloader cannot capture the changes we made +to pyproject.toml.

    +

    Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven’t created the database tables.

    +
    +
    +

    Integrate with Alembic

    +

    To get started with Alembic, run this command in the project root directory:

    +
    $ poetry run alembic init migrations
    +
    +
    +../_images/gino-fastapi-alembic.svg

    This will generate a new directory migrations where Alembic will store database +migration revisions. At the same time, an alembic.ini file is created in the project +root directory. Let’s simply add all of them to Git control.

    +

    For Alembic to use our data models defined with GINO (and of course the database +config), we need to modify migrations/env.py to connect with the GINO instance:

    +
     # add your model's MetaData object here
    + # for 'autogenerate' support
    + # from myapp import mymodel
    + # target_metadata = mymodel.Base.metadata
    +-target_metadata = None
    ++from gino_fastapi_demo.config import DB_DSN
    ++from gino_fastapi_demo.main import db, load_modules
    ++
    ++load_modules()
    ++config.set_main_option("sqlalchemy.url", str(DB_DSN))
    ++target_metadata = db
    +
    +
    +

    Then create our first migration revision with:

    +
    $ poetry run alembic revision --autogenerate -m 'add users table'
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.autogenerate.compare] Detected added table 'users'
    +  Generating migrations/versions/32c0feba61ea_add_users_table.py ...  done
    +
    +
    +

    The generated revision file should roughly look like this:

    +
    def upgrade():
    +    op.create_table(
    +        "users",
    +        sa.Column("id", sa.BigInteger(), nullable=False),
    +        sa.Column("nickname", sa.Unicode(), nullable=True),
    +        sa.PrimaryKeyConstraint("id"),
    +    )
    +
    +def downgrade():
    +    op.drop_table("users")
    +
    +
    +
    +

    Hint

    +

    Whenever there is a change to the database schema in the future, just modify the +GINO models and run alembic revision --autogenerate again to generate new +revisions to track the change. Remember to review the revision file - you may want +to adjust it.

    +
    +

    Eventually, let’s apply this migration, by upgrading to the latest revision:

    +
    $ poetry run alembic upgrade head
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.runtime.migration] Running upgrade  -> 32c0feba61ea, add users table
    +
    +
    +

    Now all the APIs should be fully operational, try with the Swagger UI.

    +
    +
    +

    Write the Tests

    +

    In order not to break our development database with running tests, let’s create a +separate database to run tests. Apply this change to gino_fastapi_demo.config:

    +
     config = Config(".env")
    +
    ++TESTING = config("TESTING", cast=bool, default=False)
    +
    + DB_DRIVER = config("DB_DRIVER", default="postgresql")
    + DB_HOST = config("DB_HOST", default=None)
    + DB_PORT = config("DB_PORT", cast=int, default=None)
    + DB_USER = config("DB_USER", default=None)
    + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    + DB_DATABASE = config("DB_DATABASE", default=None)
    ++if TESTING:
    ++    if DB_DATABASE:
    ++        DB_DATABASE += "_test"
    ++    else:
    ++        DB_DATABASE = "gino_fastapi_demo_test"
    + DB_DSN = config(
    +
    +
    +
    +

    Hint

    +

    You need to run createdb to actually create the database. If you have set +DB_DATABASE in .env - e.g. DB_DATABASE=mydb, the name of the testing +database should be mydb_test. Or else, gino_fastapi_demo_test.

    +
    +

    Then, let’s create our pytest fixture in tests/conftest.py:

    +
    import pytest
    +from alembic.config import main
    +from starlette.config import environ
    +from starlette.testclient import TestClient
    +
    +environ["TESTING"] = "TRUE"
    +
    +@pytest.fixture
    +def client():
    +    from gino_fastapi_demo.main import db, get_app
    +
    +    main(["--raiseerr", "upgrade", "head"])
    +
    +    with TestClient(get_app()) as client:
    +        yield client
    +
    +    main(["--raiseerr", "downgrade", "base"])
    +
    +
    +../_images/gino-fastapi-tests.svg

    This fixture creates all the database tables before running the test, yield a Starlette +TestClient, and drop all the tables with all the data after the test to maintain a +clean environment for the next test.

    +

    Here’s a sample test in tests/test_users.py:

    +
    import uuid
    +
    +def test_crud(client):
    +    # create
    +    nickname = str(uuid.uuid4())
    +    r = client.post("/users", json=dict(name=nickname))
    +    r.raise_for_status()
    +
    +    # retrieve
    +    url = f"/users/{r.json()['id']}"
    +    assert client.get(url).json()["nickname"] == nickname
    +
    +    # delete
    +    client.delete(url).raise_for_status()
    +    assert client.get(url).status_code == 404
    +
    +
    +

    Then run the test:

    +
    $ poetry run pytest
    +=========================== test session starts ===========================
    +platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    +rootdir: gino-fastapi-demo
    +collected 1 item
    +
    +tests/test_users.py .                                               [100%]
    +
    +============================ 1 passed in 1.21s ============================
    +
    +
    +
    +
    +

    Notes for Production

    +

    Given the popularity of Docker/Kubernetes, we’ll build a Dockerfile for our demo:

    +
    FROM python:3.8-alpine as base
    +
    +FROM base as builder
    +RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev
    +RUN pip install poetry
    +COPY . /src/
    +WORKDIR /src
    +RUN python -m venv /env && . /env/bin/activate && poetry install
    +
    +FROM base
    +RUN apk add --no-cache postgresql-libs
    +COPY --from=builder /env /env
    +COPY --from=builder /src /src
    +WORKDIR /src
    +CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"]
    +
    +
    +

    In this Dockerfile, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn with +UvicornWorker from Uvicorn as the worker class for best production reliability.

    +

    Let’s review what we have in the project.

    +../_images/gino-fastapi-layout.svg

    This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live:

    +
      +
    • Set DB_RETRY_LIMIT to a larger number to allow staring the application server +before the database is fully ready.

    • +
    • Implement the same retry logic in migrations/env.py so that Alembic gets the same +functionality.

    • +
    • Enable DB_SSL if needed.

    • +
    • Write a docker-compose.yml for other developers to get a quick taste or even use +it for development.

    • +
    • Enable CI, install pytest-cov and use --cov-fail-under to guarantee coverage.

    • +
    • Integrate static code analysis tools and security/CVE checking tools.

    • +
    • Automate Alembic upgrade properly - e.g. after new version is deployed.

    • +
    • Be aware of the common security attacks like CSRF, XSS, etc.

    • +
    • Write load tests.

    • +
    +

    Again, the source code of the demo is available here, +and the source of this tutorial is here. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking!

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.0/tutorials/tutorial.html b/docs/en/1.0/tutorials/tutorial.html new file mode 100644 index 0000000..13c47a0 --- /dev/null +++ b/docs/en/1.0/tutorials/tutorial.html @@ -0,0 +1,597 @@ + + + + + + + + GINO Basics - GINO 1.0.2.dev0 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    GINO Basics

    +

    This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of:

    + +

    Knowledge of SQLAlchemy is not required.

    +
    +

    Introduction

    +

    Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API.

    +

    You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won’t make your +code run faster, if not slower. Please read Why Asynchronous ORM? for more +information.

    +
    +
    +

    Installation

    +

    To install GINO, run this command in your terminal:

    +
    $ pip install gino
    +
    +
    +

    This is the preferred method to install GINO, as it will always install the +most recent stable release.

    +

    If you don’t have pip installed, this Python installation guide can guide +you through the process.

    +

    Alternatively, if you are using Poetry to manage your project dependencies, +you may want to run:

    +
    $ poetry add gino
    +
    +
    +
    +
    +

    Declare Models

    +

    First of all, we’ll need a Gino object, usually under the +name of db as a global variable:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +

    db acts like a reference to the database, most database interactions will +go through it.

    +

    “Model” is a basic concept in GINO, it is a Python class inherited from +db.Model. Each Model +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let’s declare a model:

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +

    By declaring this User class, we are actually defining a database table +named users, with two columns id and nickname. Note that the fixed +__tablename__ property is required. GINO +suggests singular for model names, and plural for table names. Each +db.Column property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to db types here in the SQLAlchemy +documentation.

    +
    +

    Note

    +

    SQLAlchemy is a powerful ORM library for non-asynchronous programming in +Python, on top of which GINO is built. SQLAlchemy supports many popular +RDBMS including PostgreSQL and MySQL through different dialect +implementation, so that the same Python code can be compiled into different +SQL depending on the dialect you choose. GINO inherited this support too, +but for now there is only one dialect for PostgreSQL through asyncpg.

    +
    +

    If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:

    +
    class Booking(db.Model):
    +    __tablename__ = 'bookings'
    +
    +   day = db.Column(db.Date)
    +   booker = db.Column(db.String)
    +   room = db.Column(db.String)
    +
    +   _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey')
    +   _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True)
    +   _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room')
    +
    +
    +

    It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +here in the +SQLAlchemy documentation.

    +

    Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the __table_args__ attribute. In order +to e.g. define constraints in mixin classes, +declared_attr() is required. Please feel free to read +more about it in its API documentation.

    +
    +
    +

    Get Connected

    +

    The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let’s create a +PostgreSQL database for this tutorial:

    +
    $ createdb gino
    +
    +
    +

    Then we tell our db object to connect to this database:

    +
    import asyncio
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +

    If this runs successfully, then you are connected to the newly created database. +Here postgresql indicates the database dialect to use (the default driver +is asyncpg, you can explicitly specify that with postgresql+asyncpg://, +or simply asyncpg://), localhost is where the server is, and gino +is the name of the database. Check here for more +information about how to compose this database URL.

    +
    +

    Note

    +

    Under the hood set_bind() calls +create_engine() and bind the engine to this db object. GINO +engine is similar to SQLAlchemy engine, but not identical. Because GINO +engine is asynchronous, while the other is not. Please refer to the API +reference of GINO for more information.

    +
    +

    Now that we are connected, let’s create the table in database (in the same +main() method):

    +
    await db.gino.create_all()
    +
    +
    +
    +

    Warning

    +

    It is db.gino.create_all, +not db.create_all, because +db is inherited from SQLAlchemy MetaData, +and db.create_all is from +SQLAlchemy using non-asynchronous methods, which doesn’t work with the +bound GINO engine.

    +

    In practice create_all() is usually +not an ideal solution. To manage database schema, tool like Alembic is +recommended, please see how to Use Alembic.

    +
    +

    If you want to explicitly disconnect from the database, you can do this:

    +
    await db.pop_bind().close()
    +
    +
    +

    Let’s review the code we have so far together in one piece before moving on:

    +
    import asyncio
    +from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +    await db.gino.create_all()
    +
    +    # further code goes here
    +
    +    await db.pop_bind().close()
    +
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +
    +
    +

    CRUD Operations

    +

    In order to operate on the database, one of GINO’s core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations.

    +
    +

    Create

    +

    Let’s start by creating a User:

    +
    user = await User.create(nickname='fantix')
    +# This will cause GINO to execute this SQL with parameter 'fantix':
    +# INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname
    +
    +
    +

    As mentioned previously, user object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:

    +
    print(f'ID:       {user.id}')           # 1
    +print(f'Nickname: {user.nickname}')     # fantix
    +
    +
    +

    It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:

    +
    user = User(nickname='fantix')
    +user.nickname += ' (founder)'
    +await user.create()
    +
    +
    +
    +
    +

    Retrieve

    +

    To retrieve a model object from database by primary key, you can use the class +method get() on the model class. Now let’s retrieve +the same row:

    +
    user = await User.get(1)
    +# SQL (parameter: 1):
    +# SELECT users.id, users.nickname FROM users WHERE users.id = $1
    +
    +
    +

    Normal SQL queries are done through a class property +query. For example, let’s retrieve all User +objects from database as a list:

    +
    all_users = await db.all(User.query)
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +

    Alternatively, you can use the gino extension on +query. This has exactly the same effect as above:

    +
    all_users = await User.query.gino.all()
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +
    +

    Note

    +

    User.query is actually a SQLAlchemy query, with its own +non-asynchronous execution methods. GINO added this gino extension on +all executable SQLAlchemy clause objects to conveniently execute them in +the asynchronous way, so that it is even not needed to import the db +reference for execution.

    +
    +

    Now let’s add some filters. For example, find all users with ID lower than 10:

    +
    founding_users = await User.query.where(User.id < 10).gino.all()
    +# SQL (parameter: 10):
    +# SELECT users.id, users.nickname FROM users WHERE users.id < $1
    +
    +
    +

    Read more here +about writing queries, because the query object is exactly from SQLAlchemy core.

    +
    +

    Warning

    +

    Once you get a model object, it is purely in memory and fully detached from +the database. That means, if the row is externally updated, the object +values remain unchanged. Likewise, changes made to the object won’t affect +the database values.

    +

    Also, GINO keeps no track of model objects, therefore getting the same row +twice returns two different object with identical values. Modifying one +does not magically affect the other one.

    +

    Different than traditional ORMs, the GINO model objects are more like +objective SQL results, rather than stateful ORM objects. In order to adapt +for asynchronous programming, GINO is designed to be that simple. That’s +also why GINO Is Not ORM.

    +
    +

    Sometimes we want to get only one object, for example getting the user by name +when logging in. There’s a shortcut for this scenario:

    +
    user = await User.query.where(User.nickname == 'fantix').gino.first()
    +# SQL (parameter: 'fantix'):
    +# SELECT users.id, users.nickname FROM users WHERE users.nickname = $1
    +
    +
    +

    If there is no user named “fantix” in database, user will be None.

    +

    And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +select() class method:

    +
    name = await User.select('nickname').where(User.id == 1).gino.scalar()
    +# SQL (parameter: 1):
    +# SELECT users.nickname FROM users WHERE users.id = $1
    +print(name)  # fantix
    +
    +
    +

    Or get the count of all users:

    +
    population = await db.func.count(User.id).gino.scalar()
    +# SQL:
    +# SELECT count(users.id) AS count_1 FROM users
    +print(population)  # 17 for example
    +
    +
    +
    +
    +

    Update

    +

    Then let’s try to make some modifications. In this example we’ll mixin some +retrieve operations we just tried.

    +
    # create a new user
    +user = await User.create(nickname='fantix')
    +
    +# get its name
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +assert name == user.nickname  # they are both 'fantix' before the update
    +
    +# modification here
    +await user.update(nickname='daisy').apply()
    +# SQL (parameters: 'daisy', 1):
    +# UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname
    +print(user.nickname)  # daisy
    +
    +# get its name again
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +print(name)  # daisy
    +assert name == user.nickname  # they are both 'daisy' after the update
    +
    +
    +

    So update() is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +apply() call makes the update happen in database.

    +
    +

    Note

    +

    GINO explicitly split the in-memory update and SQL update into two methods: +update() and +apply(). update() +will update the in-memory model object and return an +UpdateRequest object which contains all the +modifications. A following apply() on +UpdateRequest object will apply these recorded +modifications to database by executing a compiled SQL.

    +
    +
    +

    Tip

    +

    UpdateRequest object has another method named +update() which works the same as the one +on model object, just that it combines the new modifications together with +the ones already recorded in current UpdateRequest +object, and it returns the same UpdateRequest object. +That means, you can chain the updates and end up with one +apply(), or make use of the +UpdateRequest object to combine several updates in a +batch.

    +
    +

    update() on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the update() on model class level, with a +bit difference:

    +
    await User.update.values(nickname='Founding Member ' + User.nickname).where(
    +    User.id < 10).gino.status()
    +# SQL (parameter: 'Founding Member ', 10):
    +# UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2
    +
    +name = await User.select('nickname').where(
    +    User.id == 1).gino.scalar()
    +print(name)  # Founding Member fantix
    +
    +
    +

    There is no UpdateRequest here, everything is again +SQLAlchemy clause, its +documentation here for +your reference.

    +
    +
    +

    Delete

    +

    At last. Deleting is similar to updating, but way simpler.

    +
    user = await User.create(nickname='fantix')
    +await user.delete()
    +# SQL (parameter: 1):
    +# DELETE FROM users WHERE users.id = $1
    +print(await User.get(user.id))  # None
    +
    +
    +
    +

    Hint

    +

    Remember the model object is in memory? In the last print() +statement, even though the row is already deleted in database, the object +user still exists with its values untouched.

    +
    +

    Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):

    +
    await User.delete.where(User.id > 10).gino.status()
    +# SQL (parameter: 10):
    +# DELETE FROM users WHERE users.id > $1
    +
    +
    +

    With basic CRUD, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking!

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/.buildinfo b/docs/en/1.1b2/.buildinfo new file mode 100644 index 0000000..5c194fe --- /dev/null +++ b/docs/en/1.1b2/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: ac9737e827b4e8f4942b9648f88dd1d6 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/en/1.1b2/_images/263px-Minimum-Tonne.svg.png b/docs/en/1.1b2/_images/263px-Minimum-Tonne.svg.png new file mode 100644 index 0000000..f431e31 Binary files /dev/null and b/docs/en/1.1b2/_images/263px-Minimum-Tonne.svg.png differ diff --git a/docs/en/1.1b2/_images/archlinux.webp b/docs/en/1.1b2/_images/archlinux.webp new file mode 100644 index 0000000..3e358ef Binary files /dev/null and b/docs/en/1.1b2/_images/archlinux.webp differ diff --git a/docs/en/1.1b2/_images/community.svg b/docs/en/1.1b2/_images/community.svg new file mode 100644 index 0000000..dae6507 --- /dev/null +++ b/docs/en/1.1b2/_images/community.svg @@ -0,0 +1,26 @@ + + + + co-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/connection.png b/docs/en/1.1b2/_images/connection.png new file mode 100644 index 0000000..e54598d Binary files /dev/null and b/docs/en/1.1b2/_images/connection.png differ diff --git a/docs/en/1.1b2/_images/docs.webp b/docs/en/1.1b2/_images/docs.webp new file mode 100644 index 0000000..20259b1 Binary files /dev/null and b/docs/en/1.1b2/_images/docs.webp differ diff --git a/docs/en/1.1b2/_images/engine.png b/docs/en/1.1b2/_images/engine.png new file mode 100644 index 0000000..6b64561 Binary files /dev/null and b/docs/en/1.1b2/_images/engine.png differ diff --git a/docs/en/1.1b2/_images/exchangeratesapi.webp b/docs/en/1.1b2/_images/exchangeratesapi.webp new file mode 100644 index 0000000..dc2657a Binary files /dev/null and b/docs/en/1.1b2/_images/exchangeratesapi.webp differ diff --git a/docs/en/1.1b2/_images/explanation.svg b/docs/en/1.1b2/_images/explanation.svg new file mode 100644 index 0000000..ef775d1 --- /dev/null +++ b/docs/en/1.1b2/_images/explanation.svg @@ -0,0 +1,19 @@ + + + + ex-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-alembic.svg b/docs/en/1.1b2/_images/gino-fastapi-alembic.svg new file mode 100644 index 0000000..0cfc4b1 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-alembic.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    alembic.ini
    ale...
    migrations
    migra...
    env.py
    env...
    versions
    versi...
    32c0feba61ea_add_users_table.py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-config.svg b/docs/en/1.1b2/_images/gino-fastapi-config.svg new file mode 100644 index 0000000..673d084 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-config.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    config.py
    con...
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-env.svg b/docs/en/1.1b2/_images/gino-fastapi-env.svg new file mode 100644 index 0000000..4ec9e4e --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-env.svg @@ -0,0 +1,3 @@ + + +
    .env
    .env
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-layout.svg b/docs/en/1.1b2/_images/gino-fastapi-layout.svg new file mode 100644 index 0000000..07f4701 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-layout.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    .env
    .env
    models
    models
    __init__.py
    __i...
    users.py
    use...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    __init__.py
    __i...
    asgi.py
    asg...
    config.py
    con...
    main.py
    mai...
    tests
    tests
    conftest.py
    con...
    test_users.py
    tes...
    migrations
    migra...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    The project root directory.

    Alembic data directory.

    Database migration revisions directory.

    One of the revisions.

    Alembic Python environment.

    Application source code container.

    Project root python package.

    Database models and GINO instance.

    GINO instance (SQLAlchemy Metadata).

    Models for users.

    API implementation.



    User-related APIs.



    ASGI entry point.

    Starlette-style application configuration.

    Application initialization.

    Testing code.

    pytest fixtures.

    User-related tests.

    Local config, not in Git control.

    Alembic entry config.

    Production Docker image.

    Poetry-frozen dependency versions.

    Project and dependency definition.
    The project root directory....
    alembic.ini
    ale...
    Dockerfile
    Doc...
    env.py
    env...
    versions
    versi...
    32c0feba61ea....py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-models-users.svg b/docs/en/1.1b2/_images/gino-fastapi-models-users.svg new file mode 100644 index 0000000..886d581 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-models-users.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-models.svg b/docs/en/1.1b2/_images/gino-fastapi-models.svg new file mode 100644 index 0000000..4dc983c --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-models.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-poetry.svg b/docs/en/1.1b2/_images/gino-fastapi-poetry.svg new file mode 100644 index 0000000..e0da8c1 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-poetry.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-src.svg b/docs/en/1.1b2/_images/gino-fastapi-src.svg new file mode 100644 index 0000000..7310163 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-src.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    __init__.py
    __i...
    asgi.py
    asg...
    main.py
    mai...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-tests.svg b/docs/en/1.1b2/_images/gino-fastapi-tests.svg new file mode 100644 index 0000000..53673d8 --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-tests.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    tests
    tests
    test_users.py
    tes...
    conftest.py
    con...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi-views.svg b/docs/en/1.1b2/_images/gino-fastapi-views.svg new file mode 100644 index 0000000..7321a7a --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi-views.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/gino-fastapi.svg b/docs/en/1.1b2/_images/gino-fastapi.svg new file mode 100644 index 0000000..9d365ba --- /dev/null +++ b/docs/en/1.1b2/_images/gino-fastapi.svg @@ -0,0 +1,3 @@ + + +
    Gunicorn
    Gunicorn
    Uvicorn
    Uvicorn
    Starlette
    Starlette
    FastAPI
    FastAPI
    API Implementation
    API Implementation
    GINO Models
    GINO Models
    GINO with Starlette
    GINO with Starlette
    SQLAlchemy core
    SQLAlchemy core
    asyncpg
    asyncpg
    Alembic
    Alembic
    psycopg2
    psycopg2
    PostgreSQL
    PostgreSQL
    Application Server
    Application Server
    ASGI Middleware
    ASGI Middleware
    Web Framework
    Web Framework
    Tutorial Code
    Tutorial Code
    GINO
    GINO
    Database Library
    Database Library
    HTTP API
    HTTP API
    DB Migration CLI
    DB Migration CLI
    Legend
    Legend
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/1.1b2/_images/github.svg b/docs/en/1.1b2/_images/github.svg new file mode 100644 index 0000000..cb2484b --- /dev/null +++ b/docs/en/1.1b2/_images/github.svg @@ -0,0 +1,27 @@ + + + + sourcecode + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/happy-hacking.png b/docs/en/1.1b2/_images/happy-hacking.png new file mode 100644 index 0000000..701e7bd Binary files /dev/null and b/docs/en/1.1b2/_images/happy-hacking.png differ diff --git a/docs/en/1.1b2/_images/how-to.svg b/docs/en/1.1b2/_images/how-to.svg new file mode 100644 index 0000000..7461024 --- /dev/null +++ b/docs/en/1.1b2/_images/how-to.svg @@ -0,0 +1,22 @@ + + + + guide + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/open-source.svg b/docs/en/1.1b2/_images/open-source.svg new file mode 100644 index 0000000..0946f0a --- /dev/null +++ b/docs/en/1.1b2/_images/open-source.svg @@ -0,0 +1,18 @@ + + + + bsd + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/python-gino.webp b/docs/en/1.1b2/_images/python-gino.webp new file mode 100644 index 0000000..f6c11ac Binary files /dev/null and b/docs/en/1.1b2/_images/python-gino.webp differ diff --git a/docs/en/1.1b2/_images/python.svg b/docs/en/1.1b2/_images/python.svg new file mode 100644 index 0000000..f62b29b --- /dev/null +++ b/docs/en/1.1b2/_images/python.svg @@ -0,0 +1,22 @@ + + + + download + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/reference.svg b/docs/en/1.1b2/_images/reference.svg new file mode 100644 index 0000000..0c5f656 --- /dev/null +++ b/docs/en/1.1b2/_images/reference.svg @@ -0,0 +1,19 @@ + + + + re-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/tutorials.svg b/docs/en/1.1b2/_images/tutorials.svg new file mode 100644 index 0000000..2eb2111 --- /dev/null +++ b/docs/en/1.1b2/_images/tutorials.svg @@ -0,0 +1,26 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_images/why_coroutine.png b/docs/en/1.1b2/_images/why_coroutine.png new file mode 100644 index 0000000..99c743a Binary files /dev/null and b/docs/en/1.1b2/_images/why_coroutine.png differ diff --git a/docs/en/1.1b2/_images/why_multicore.png b/docs/en/1.1b2/_images/why_multicore.png new file mode 100644 index 0000000..c209a9f Binary files /dev/null and b/docs/en/1.1b2/_images/why_multicore.png differ diff --git a/docs/en/1.1b2/_images/why_multithreading.png b/docs/en/1.1b2/_images/why_multithreading.png new file mode 100644 index 0000000..e0e4321 Binary files /dev/null and b/docs/en/1.1b2/_images/why_multithreading.png differ diff --git a/docs/en/1.1b2/_images/why_single_task.png b/docs/en/1.1b2/_images/why_single_task.png new file mode 100644 index 0000000..54fab08 Binary files /dev/null and b/docs/en/1.1b2/_images/why_single_task.png differ diff --git a/docs/en/1.1b2/_images/why_throughput.png b/docs/en/1.1b2/_images/why_throughput.png new file mode 100644 index 0000000..388f533 Binary files /dev/null and b/docs/en/1.1b2/_images/why_throughput.png differ diff --git a/docs/en/1.1b2/_sources/explanation.rst.txt b/docs/en/1.1b2/_sources/explanation.rst.txt new file mode 100644 index 0000000..6d91357 --- /dev/null +++ b/docs/en/1.1b2/_sources/explanation.rst.txt @@ -0,0 +1,7 @@ +Explanation +=========== + +.. toctree:: + :glob: + + explanation/* diff --git a/docs/en/1.1b2/_sources/explanation/async.rst.txt b/docs/en/1.1b2/_sources/explanation/async.rst.txt new file mode 100644 index 0000000..a857170 --- /dev/null +++ b/docs/en/1.1b2/_sources/explanation/async.rst.txt @@ -0,0 +1,214 @@ +Asynchronous Programming 101 +============================ + + +The Story +--------- + +Let's say we want to build a search engine. We'll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this: + +.. image:: ../images/why_single_task.png + :align: center + +We have lots of web pages to index, so we simply handle them one by one: + +.. image:: ../images/why_throughput.png + :align: center + +Let's assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores: + +.. image:: ../images/why_multicore.png + :align: center + +This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading: + +.. image:: ../images/why_multithreading.png + :align: center + +Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What's wrong with multi-threading? From the diagram we can see: + +* There are yellow bars taking up extra time. +* The green bars can still overlap with any bar in the other thread, but +* non-green bars cannot overlap with non-green bars in the other thread. + +The yellow bars are time taken by `context switches +`_, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let's assume a world without +`Hyper-threading `_ or similar), +so in order to run several threads concurrently the CPU must `split its +time `_ into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point. + +Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it's waiting for the HTTP response (I/O). That's how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won't be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That's also why this is called concurrency instead of parallelism. + +As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous `C10k +problem `_, usually solved by +asynchronous I/O: + +.. image:: ../images/why_coroutine.png + :align: center + +.. note:: + + Asynchronous I/O and coroutines are two different things, but they usually + go together. Here we will stick with coroutines for simplicity. + +Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem. + + +Cooperative multitasking +------------------------ + +So what is a coroutine? + +In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread. + +Threads are scheduled by the operating system using an approach called `preemptive +multitasking `_. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn't +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this: + +.. code-block:: none + + Thread 1: I wanna run! + OS: Okay, here you go... + Thread 2: I wanna run! + OS: Urh, alright one sec ... Thread 1, hold on for a while! + Thread 1: Well I'm not done yet, but you are the boss. + OS: It won't be long. Thread 2 it's your turn now. + Thread 2: Yay! (&%#$@..+*&#) + Thread 1: Can I run now? + OS: Just a moment please ... Thread 2, give it a break! + Thread 2: Alright ... but I really need the CPU. + OS: You'll have it later. Thread 1, hurry up! + +In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don't - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +`cooperative multitasking +`_. It's like this: + +.. code-block:: none + + Coroutine 1: Let me know when event A arrives. I'm done here before that. + Event manager: Okay. What about you, coroutine 2? + Coroutine 2: Um I've got nothing to do here before event B. + Event manager: Cool, I'll be watching. + Event manager: (after a while) Hey coroutine 1, event A is here! + Coroutine 1: Awesome! Let me see ... looks good, but I need event C now. + Event manager: Very well. Seems event B arrived just now, coroutine 2? + Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done. + Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while. + (silence) + Event manager: Damn, event C timed out! + Coroutine 1: Arrrrh gotta kill myself with an exception :S + Event manager: Up to you :/ + +For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That's why in the last diagram, +the red bars are not interleaved like threads. + +.. tip:: + + In Python and asyncio, ``async def`` declares coroutines, ``await`` yields + control to event loop (event manager). + + +Pros and cons +------------- + +Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput. + +With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code. + +However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple ``recv()`` operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to ``recv()``, repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop_ this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O. + +Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, ``sleep(1)`` +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between ``await`` finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind. + +Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It's not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure. + + +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/en/1.1b2/_sources/explanation/engine.rst.txt b/docs/en/1.1b2/_sources/explanation/engine.rst.txt new file mode 100644 index 0000000..ebd59ed --- /dev/null +++ b/docs/en/1.1b2/_sources/explanation/engine.rst.txt @@ -0,0 +1,521 @@ +===================== +Engine and Connection +===================== + +:class:`~gino.engine.GinoEngine` is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together: + +.. image:: ../images/engine.png + :align: center + +Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users. + +During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use. + +.. note:: + + In SQLAlchemy, database drivers are supposed to follow the DB-API standard, + which does not usually provide a pool implementation. Therefore, SQLAlchemy + has its own pool implementation, created directly in engine. This is where + this diagram doesn't fit SQLAlchemy. + +The pool creates raw connections, not the :class:`~gino.engine.GinoConnection` +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries. + +On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use. + +.. note:: + + Another difference to SQLAlchemy here: GINO execution methods always return + final results, while in SQLAlchemy accessing the result may cause further + implicit database accesses. Therefore GINO engine immediately releases the + connection when the execution method on the engine returns, but SQLAlchemy + can only release the connection implicitly when the result data is found + exhausted. + + By immediately releasing a connection, GINO may not release the related raw + connection when the raw connection was reused from another parent + connection. We'll get to this later. + +GINO also supports `implicit execution +`_ +without having to specify an engine or connection explicitly. This is done by +binding the engine to the ``db`` instance, also known as the +:class:`~sqlalchemy.schema.MetaData` or the :class:`~gino.api.Gino` instance. +You may possibly bind a :class:`~gino.engine.GinoConnection` instance, but that +is greatly not recommended because it is very much untested. + +At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +``db`` instance on creation, therefore the models can do implicit execution too +if their ``db`` has a bind. + +Then let's get to some details. + + +Creating Engines +---------------- + +GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous :class:`~gino.engine.GinoEngine` is +just ``gino``, but only available after ``gino`` is imported:: + + import gino, sqlalchemy + + async def main(): + e = await sqlalchemy.create_engine('postgresql://...', strategy='gino') + # e is a GinoEngine + +.. tip:: + + Please read `this SQLAlchemy document + `_ + to learn about writing database URLs. + +Also the GINO strategy replaces the default driver of dialect ``postgresql://`` +from ``psycopg2`` to ``asyncpg``, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +``postgresql+asyncpg://...`` or just ``asyncpg://...``. + +GINO also offers a shortcut as :func:`gino.create_engine`, which only sets the +default strategy to ``gino`` and does nothing more. So here is an identical +example:: + + import gino + + async def main(): + e = await gino.create_engine('postgresql://...') + # e is also a GinoEngine + +As you may have noticed, when using the GINO strategy, +:func:`~sqlalchemy.create_engine` returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default. + +For it is just SQLAlchemy :func:`~sqlalchemy.create_engine`, the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!): + +For Dialect: + +* `isolation_level `_ +* `paramstyle `_ + +For Engine: + +* `echo `_ +* `execution_options `_ +* `logging_name `_ + +While these parameters are discarded by GINO: + +* `module `_ + +In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from :func:`~asyncpg.pool.create_pool`. +For example, we can create an engine without initial connections:: + + e = await gino.create_engine('postgresql://...', min_size=0) + +Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:: + + import sqlalchemy + + metadata = sqlalchemy.MetaData() + metadata.bind = 'postgresql://...' + + # or in short + + metadata = sqlalchemy.MetaData('postgresql://...') + +This implicitly calls :func:`~sqlalchemy.create_engine` under the hood. However +in GINO, creating an engine requires ``await``, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass :class:`~gino.api.Gino`, reverted it to simple assignment:: + + import gino + + db = gino.Gino() + + async def main(): + # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind + engine = await gino.create_engine('postgresql://...') + db.bind = engine + +And provided a shortcut to do so:: + + engine = await db.set_bind('postgresql://...') + +And another simpler shortcut for one-time usage:: + + db = await gino.Gino('postgresql://...') + +To unset a bind and close the engine:: + + engine, db.bind = db.bind, None + await engine.close() + +Or with a shortcut correspondingly:: + + await engine.pop_bind().close() + +Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +Managing Connections +-------------------- + +With a :class:`~gino.engine.GinoEngine` at hand, you can acquire connections +from the pool now:: + + conn = await engine.acquire() + +Don't forget to release it after use:: + + await conn.release() + +Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:: + + async with engine.acquire() as conn: + # play with the connection + +Here ``conn`` is a :class:`~gino.engine.GinoConnection` instance. As mentioned +previously, :class:`~gino.engine.GinoConnection` is mapped to an underlying raw +connection, as shown in following diagram: + +.. image:: ../images/connection.png + :align: center + +Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: ``reuse`` and +``lazy``. They are keyword arguments on :meth:`~gino.engine.GinoEngine.acquire` +and by default switched off. + +reuse +""""" + +When acquiring a :class:`~gino.engine.GinoConnection` (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +:class:`~gino.engine.GinoConnection` (2). This is the default behavior of +:meth:`~gino.engine.GinoConnection.acquire` with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:: + + async with engine.acquire() as conn1: + async with engine.acquire() as conn2: + # conn2 is a completely different connection than conn1 + +But sometimes ``conn2`` may exist in a different method:: + + async def outer(): + async with engine.acquire() as conn1: + await inner() + + async def inner(): + async with engine.acquire() as conn2: + # ... + +And we probably wish ``inner`` could reuse the same raw connection in +``outer`` to save some resource, or borrow a new one if ``inner`` is +individually called without ``outer``:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(conn2=None): + if conn2 is None: + async with engine.acquire() as conn2: + # ... + else: + # the same ... again + +This is exactly the scenario ``reuse`` could be useful. We can simply tell the +:meth:`~gino.engine.GinoConnection.acquire` to reuse the most recent reusable +connection in current context by setting ``reuse=True``, as presented in this +identical example:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(): + async with engine.acquire(reuse=True) as conn2: + # ... + +Back to previous diagram, the blue :class:`~gino.engine.GinoConnection` +instances (3, 4, 6) are "reusing connections" acquired with ``reuse=True``, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that ``acquire(reuse=True)`` always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack. + +.. tip:: + + By context, we are actually referring to the context concept in + `contextvars `_ the + new module in Python 3.7, and its partial backport `aiocontextvars + `_. Simply speaking, you may + treat a series of function calls in a chain as in the same context, even if + there is an ``await``. It's something like a thread local in asyncio. + +:class:`~gino.engine.GinoConnection` (2) may be created through +``acquire(reuse=True)`` too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection. + +Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +:class:`~gino.engine.GinoConnection` can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more. + +lazy +"""" + +As you may have found, :class:`~gino.engine.GinoConnection` (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set ``lazy=True`` on acquire. + +A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, :class:`~gino.engine.GinoConnection` (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + +On implementation level, ``lazy`` is extremely easy in +:meth:`~gino.engine.GinoEngine.acquire`: if ``lazy=False`` then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, :class:`~gino.egnine.GinoConnection` will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all :class:`~gino.engine.GinoConnection` +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + await conn.release(permanent=False) # release (8) + await asyncio.sleep(10) # simulate long I/O work + await conn.scalar('select now()') # re-acquire a new raw connection, + # not necessarily the same (8) + +When used together with ``reuse``, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set ``lazy=False`` on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again: + +.. image:: ../images/connection.png + :align: center + + +reusable +"""""""" + +Usually, you don't have to worry about the two options ``reuse`` and ``lazy``, +using the default :meth:`~gino.engine.GinoEngine.acquire` will always create +a concrete :class:`~gino.engine.GinoConnection` with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use ``reusable=False`` on acquire. As shown in the diagram, the unreusable +:class:`~gino.engine.GinoConnection` is an orphan away from any stack:: + + async with engine.acquire(): # (2) + async with engine.acquire(reusable=False): # the unreusable connection + async with engine.acquire(reuse=True): # (3) + +Unreusable connections can be lazy. But it is usually meaningless to specify +both ``reuse=True`` and ``reusable=False`` at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +``acquire(reuse=True, reusable=False)`` unless you know what it does. + + +current_connection +"""""""""""""""""" + +Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +:attr:`~gino.engine.GinoEngine.current_connection` which is always the reusable +:class:`~gino.engine.GinoConnection` at the top of current stack, or ``None`` +if current stack is empty. + +.. tip:: + + The different between :attr:`~gino.engine.GinoEngine.current_connection` + and :meth:`acquire(reuse=True) ` is, the + latter always produces a :class:`~gino.engine.GinoConnection`, while the + former may not. + + +Executing Queries +----------------- + +Once you have a :class:`~gino.engine.GinoConnection` instance, you can start +executing queries with it. There are 6 variants of the execute method: +:meth:`~gino.engine.GinoConnection.all`, +:meth:`~gino.engine.GinoConnection.first`, +:meth:`~gino.engine.GinoConnection.one`, +:meth:`~gino.engine.GinoConnection.one_or_none`, +:meth:`~gino.engine.GinoConnection.scalar` and +:meth:`~gino.engine.GinoConnection.status`. They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results: + +* :meth:`~gino.engine.GinoConnection.all` returns all results in a + :class:`list`, which may be empty when the query has no result, empty but + still a :class:`list`. +* :meth:`~gino.engine.GinoConnection.first` returns the first result directly, + or ``None`` if there is no result at all. There is usually some optimization + behind the scene to efficiently get only the first result, instead of loading + the full result set into memory. +* :meth:`~gino.engine.GinoConnection.one` returns exactly one result. If there + is no result at all or if there are multiple results, an exception is raised. +* :meth:`~gino.engine.GinoConnection.one_or_none` is similar to + :meth:`~gino.engine.GinoConnection.one`, but it returns ``None`` if there is + no result instead or raising an exception. +* :meth:`~gino.engine.GinoConnection.scalar` is similar to + :meth:`~gino.engine.GinoConnection.first`, it returns the first value of the + first result. Quite convenient to just retrieve a scalar value from database, + like ``NOW()``, ``MAX()``, ``COUNT()`` or whatever generates a single value. + ``None`` is also returned when there is no result, it is up to you how to + distinguish no result and the first value is ``NULL``. +* :meth:`~gino.engine.GinoConnection.status` executes the query and discard all + the query results at all. Instead it returns the execution status line as it + is, usually a textual string. Note, there may be no optimization to only + return the status without loading the results, so make your query generate + nothing if you don't want any result. + +By "result", I meant :class:`~sqlalchemy.engine.RowProxy` of SQLAlchemy - an +immutable row instance with both :class:`tuple` and :class:`dict` interfaces. +Database values are translated twice before they are eventually stored in a +:class:`~sqlalchemy.engine.RowProxy`: first by the database driver (dialect) +from network payload to Python objects (see `Type Conversion +`_ of +how asyncpg does this), second by SQLAlchemy +:meth:`~sqlalchemy.types.TypeEngine.result_processor` depending on the actual +type and dialect. + +The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy :meth:`~sqlalchemy.engine.Connection.execute` (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +``multiparams``, all 4 methods will always return ``None`` discarding all +results. Likewise, the parameter values are processed twice too: first by +:meth:`~sqlalchemy.types.TypeEngine.bind_processor` then the database driver. + +GINO also supports SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execution_options` provided either on +:meth:`engine level `, +:meth:`connection level ` or on +:meth:`queries `. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling ``return_model`` and providing a ``model`` will make +:meth:`~gino.engine.GinoConnection.all` and +:meth:`~gino.engine.GinoConnection.first` return ORM model instance(s) instead +of :class:`~sqlalchemy.engine.RowProxy` instance(s). See also +:meth:`~sqlalchemy.engine.Connection.execution_options` for more information. + +In addition, GINO has an :meth:`~gino.engine.GinoConnection.iterate` method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a `server-side cursor +`_. + + +Implicit Execution +------------------ + +Acquire a :class:`~gino.engine.GinoConnection` and execute queries on it, that +is the most explicit way. You can also execute queries on a +:class:`~gino.engine.GinoEngine` instance. In this case, a connection will be +acquired with ``reuse=True`` for you implicitly, and released after returning:: + + await engine.scalar('select now()') + +Equals to:: + + async with engine.acquire(reuse=True) as conn: + await conn.scalar('select now()') + +This allows you to easily write connectionless code. For example:: + + async def get_now(): + return await engine.scalar('select now()') + + async def main(): + async with engine.acquire(): + now = await get_now() + await engine.status('UPDATE ...') + +In this example, ``main()`` will take only one raw connection. ``get_now()`` +can also work alone out of any ``acquire()`` context, thanks to ``reuse``. + +Furthermore, GINO provides the same query APIs on :class:`~gino.api.Gino` +directly. They are simply delegates to corresponding API methods on the +``bind``. This allows even engine-less programming:: + + db = gino.Gino() + + async def get_now(): + return await db.scalar('select now()') + + async def main(): + async with db.with_bind('postgresql://...'): + now = await get_now() + await db.status('UPDATE ...') + +.. note:: + + In this example we didn't put the two queries in an ``acquire()`` block, so + they might be executed in two different connections. + +At last, the SQLAlchemy `implicit execution +`_ +on queries also work in GINO, under an extension named ``gino``:: + + await users_table.select().gino.all() + +By default, the extension :class:`~gino.api.GinoExecutor` is injected on +:class:`~sqlalchemy.sql.expression.Executable` as a property of name ``gino`` +at the creation of :class:`~gino.api.Gino` instance. Therefore, any +:class:`~sqlalchemy.sql.expression.Executable` object has the ``gino`` +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the ``bind`` of the ``db`` instance. diff --git a/docs/en/1.1b2/_sources/explanation/sa20.rst.txt b/docs/en/1.1b2/_sources/explanation/sa20.rst.txt new file mode 100644 index 0000000..0c18c1f --- /dev/null +++ b/docs/en/1.1b2/_sources/explanation/sa20.rst.txt @@ -0,0 +1,691 @@ +SQLAlchemy 2.0 +============== + +This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes. + +`SQLAlchemy 2.0 `__ will +deliver many breaking API changes, and `SQLAlchemy 1.4 +`__ will be the "interim" +version for people to eventually upgrade their software to use SQLAlchemy 2.0. + ++-------+------------+----------+----------------------------+ +| GINO | SQLAlchemy | Dialect | Comments | ++=======+============+==========+============================+ +| 1.0.x | 1.3.x | Custom | Current (old-)stable. | ++-------+------------+----------+----------------------------+ +| 1.1.x | 1.3.x | Custom | Next old-stable. | ++-------+------------+----------+----------------------------+ +| 1.2.x | 1.3.x | Custom | Future old-stable (maybe). | ++-------+------------+----------+----------------------------+ +| 1.4.x | 1.4.x | Upstream | 2.0 Interim. | ++-------+------------+----------+----------------------------+ +| 2.0.x | 2.0.x | Upstream | Future stable. | ++-------+------------+----------+----------------------------+ +| 2.1.x | 2.0.x | Upstream | Future stable iterations. | ++-------+------------+----------+----------------------------+ + +To make things easier, GINO will (luckily) also `follow the same versions +`__ for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only. + +At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won't match SQLAlchemy versions. + + +The Async Solution +------------------ + +Among all the exciting updates in SQLAlchemy 1.4 / 2.0, `native async support +`__ is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet_ to mix asynchronous stuff into current code base, avoiding making everything +async. + +Let's say we have an asynchronous method to create an asyncpg connection:: + + import asyncpg + + async def connect(): + return await asyncpg.connect("postgresql:///") + +And an end-user method to use it:: + + async def main(): + conn = await connect() + now = await conn.fetchval("SELECT now()") + +Now instead of directly calling ``connect()`` from ``main()``, I would like to add some +additional logic - let's say, a sanity check:: + + async def safe_connect(): + conn = await connect() + try: + await conn.execute("SELECT 1") + except Exception: + return None + else: + return conn + +Then the end-user should modify ``main()`` to: + +.. code-block:: python + :emphasize-lines: 2,3 + + async def main(): + conn = await safe_connect() + if conn: + now = await conn.fetchval("SELECT now()") + +OK, everything works so far, as they are all regular async code. Here's the interesting +part: ``safe_connect()`` must not be an ``async def`` method. With SQLAlchemy 1.4+, we +could: + +.. code-block:: python + :emphasize-lines: 1,3,4,6,13 + + from sqlalchemy.util import await_only, greenlet_spawn + + def sync_safe_connect(): + conn = await_only(connect()) + try: + await_only(conn.execute("SELECT 1")) + except Exception: + return None + else: + return conn + + async def safe_connect(): + return await greenlet_spawn(sync_safe_connect) + +Behind the scene, ``greenlet_spawn()`` runs the given "sync" method in a greenlet, which +uses ``await_only()`` to switch to the event loop and bridge the underlying async +methods. As ``sync_safe_connect()`` is just a normal Python method, you can imagine how +it works together with lots of other "sync" code asynchronously. + +We're not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its "sync" code base, and still being able to provide async +APIs on top of them. + + +Async SQLAlchemy +---------------- + +Although greenlet_ might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move. + +The sync library existed for years, with many assumptions like using ``threading.Lock`` +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. ``asyncio.Lock``. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues. + +As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, ``threading.Lock.acquire()`` actually works fine in a single coroutine, but `2 +concurrent coroutines `__ +acquiring the same ``threading.Lock`` may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread. + +Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base. + +However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that's GINO's part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won't work +because there is no place for ``await`` in accessing attributes (GINO doesn't like such +implicitness anyways, so yeah). + +To quickly get a picture of async SQLAlchemy (Core), here's a sample from SQLAlchemy:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + + async with engine.begin() as conn: + await conn.run_sync(meta.drop_all) + await conn.run_sync(meta.create_all) + + await conn.execute( + t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}] + ) + + async with engine.connect() as conn: + + # select a Result, which will be delivered with buffered + # results + result = await conn.execute(select(t1).where(t1.c.name == "some name 1")) + + print(result.fetchall()) + + + asyncio.run(async_main()) + + +Auto-Commit Complication +------------------------ + +After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that ``BEGIN`` starts a transaction and +``COMMIT`` / ``ROLLBACK`` ends it. But what is happening to SQL statements that is not +wrapped in ``BEGIN ... COMMIT`` blocks? + + If you do not issue a ``BEGIN`` command, then each individual statement has an + implicit ``BEGIN`` and (if successful) ``COMMIT`` wrapped around it. + + -- PostgreSQL Documentation, `3.4. Transactions + `__ + +And yes, implicit ``ROLLBACK`` if not successful. This is not directly named as an +"auto-commit" feature, but PostgreSQL does enforce it. Now imagine a database whose +"auto-commit" feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed. + +`PEP 249 `__ (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only ``commit()`` and ``rollback()`` on a +connection, but no ``begin()``. So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call ``commit()`` to persist your changes. Closing a connection will +cause pending transactions rolled back automatically. + + Note that if the database supports an auto-commit feature, this (*the auto-commit + feature -- GINO comments*) must be initially off. + + -- PEP 249 + +As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +"auto-commit" has to mimic such behavior. For example, psycopg2_ will automatically emit +a ``BEGIN`` to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen ``IDLE IN TRANSACTION``?), sometimes even +holding database locks and eventually causing a deadlock storm. + +To work around this workaround, PEP 249 does say: + + An interface method may be provided to turn it (the auto-commit feature) back on. + +So for psycopg2_, one could do this:: + + import psycopg2 + + conn = psycopg2.connect("postgresql:///") + conn.autocommit = True + conn.cursor().execute("SELECT now()") + +Now the database correctly receives this ``SELECT`` statement only, without any implicit +``BEGIN`` surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:: + + conn.cursor().execute("BEGIN") + conn.cursor().execute("UPDATE ...") + conn.cursor().execute("COMMIT") + +Or 2) turn auto-commit off again:: + + conn.autocommit = False + conn.cursor().execute("UPDATE ...") + conn.commit() + +I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg_ does provide a cleaner API, by not complying to PEP 249:: + + import asyncpg + + async def main(): + conn = await asyncpg.connect("postgresql://") + + print(await conn.fetchval("SELECT now()")) # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.execute("UPDATE ...") # UPDATE ...; + # COMMIT; + +It's much cleaner to see what's actually happening on the wire to the database. This is +also how GINO works. + + +SQLAlchemy for DB-API +--------------------- + +Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:: + + import sqlalchemy as sa + + e = sa.create_engine("postgresql:///", future=True) + with e.connect() as conn: + conn.scalar(sa.text("SELECT now()")) + +Only ``SELECT now()``? No. Here's the answer:: + + with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + conn.scalar(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +.. note:: + + We are using SQLAlchemy 2.0 API for simplification, by setting ``future=True`` using + SQLAlchemy 1.4. It's way more complicated in SQLAlchemy 1.3 and I don't want to get + into that. + +The reason behind this is, connections have "auto-commit" turned off by default. If +there is no transaction, an implicit ``BEGIN`` will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg_ +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg_ and +simulated a compatible DB-API. Like this:: + + import sqlalchemy as sa + from sqlalchemy.ext.asyncio import create_async_engine + + async def main(): + e = create_async_engine("postgresql+asyncpg:///") + async with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +If you want to modify the database permanently, you have to ``commit()`` the implicit +transaction explicitly: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("UPDATE ...")) # BEGIN; UPDATE ...; + await conn.commit() # COMMIT; + +Or use the explicit transaction API: + +.. code-block:: python + :emphasize-lines: 4,6 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Please note that, SQLAlchemy 2.0 doesn't allow soft-nested transactions. In other words, +you cannot nest 2 ``async with conn.begin():`` blocks like this: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + async with conn.begin(): # Error: a transaction is already begun + ... + +This limitation applies to implicit transactions too, even though it's weird: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + await conn.rollback() # ROLLBACK; + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Similar to Core, SQLAlchemy ORM `follows the same principal +`__. Grab a session, use +it without ``begin()``, and when you want to commit, ``commit()``. Or, use an explicit +transaction in a ``with session.begin():`` block. Personally I don't like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more. + + +SQLAlchemy AUTOCOMMIT +--------------------- + +I know you already miss the WYSIWYG asyncpg_ and GINO API. Hang in there, let's build +GINO 1.4 together with the `SQLAlchemy AUTOCOMMIT feature +`__. + +To turn AUTOCOMMIT back on, we need to set the ``isolation_level`` to ``AUTOCOMMIT`` in +``execution_options``: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Hooray! No more implicit ``BEGIN`` magic. We're one step closer. + +.. note:: + + There is also a keyword argument: + + .. code-block:: python + :emphasize-lines: 3 + + e = create_async_engine( + "postgresql+asyncpg:///", + isolation_level="AUTOCOMMIT", + ) + + But this is implemented very differently than ``execution_options``, and I don't + think it's working for GINO's use case. + +The next question is, how do we explicitly start a transaction? Let's try ``begin()``: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +Wait a second ... we've seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we're in AUTOCOMMIT mode? Even +though the ``isolation_level`` tell the driver not to send ``BEGIN`` to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction: + +.. code-block:: python + :emphasize-lines: 5,6 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.begin(): # no-op + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + +Well, not quite what we expected. With AUTOCOMMIT set, all of ``begin()``, ``commit()`` +and ``rollback()`` become no-ops. + +Similar to the answers in psycopg2_, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let's try 2): + +.. code-block:: python + :emphasize-lines: 6-8 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +It's working! According to SQLAlchemy docs, ``execution_options()`` creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well... + +.. code-block:: python + :emphasize-lines: 11,12 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same "DB-API" connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting ``isolation_level`` +modifies the value on "DB-API" connection. + +Returning a SQLAlchemy connection back to the pool resets the ``isolation_level`` to its +default value, and acquiring the same connection again will initialize the +``isolation_level`` with values from ``execution_options`` of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +``isolation_level`` again: + +.. code-block:: python + :emphasize-lines: 11 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + conn.execution_options(isolation_level="AUTOCOMMIT") + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Eventually we made it! 🎉 + +Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:: + + import gino + + async def main(): + engine = await gino.create_engine("postgresql:///") + async with engine.acquire() as conn: + await conn.scalar("SELECT now()") # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.status("UPDATE ...") # UPDATE ...; + # COMMIT; + +.. hint:: + + Now I feel that "implementing" auto-commit feature is more like restoring to the + original database behavior, and having auto-commit turned off by default should be + considered as a new feature called "auto-begin" or "implicit transaction". And it's + a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem. + + +Isolation Levels +---------------- + +By far, we only used 2 ``isolation_level`` values: + +* ``AUTOCOMMIT`` +* ``READ COMMITTED`` + +``AUTOCOMMIT`` is not a valid PostgreSQL isolation level. It's only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the "auto-begin" simulation. + +``READ COMMITTED`` is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction: + +.. code-block:: plpgsql + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +To start a transaction in a different isolation level, you may: + +.. code-block:: plpgsql + :emphasize-lines: 1,7 + + # BEGIN ISOLATION LEVEL SERIALIZABLE; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + +As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit ``BEGIN`` in place, an implicit transaction is used. So this SQL also works +individually: + +.. code-block:: plpgsql + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + +But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels: + +.. code-block:: plpgsql + :emphasize-lines: 1,7,10,16,22,28 + + # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE; + SET + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + + # BEGIN ISOLATION LEVEL READ COMMITTED; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +Then let's see how SQLAlchemy with asyncpg solves this problem: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "SERIALIZABLE"}, + ) + async with e.connect() as conn: + async with conn.begin(): # BEGIN ISOLATION LEVEL SERIALIZABLE; + await conn.execute(sa.text("UPDATE ...")) # UPDATE ...; + # COMMIT; + +Under the neath, SQLAlchemy is leveraging asyncpg's +``Connection.transaction(isolation="...")`` to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions. + +But there are 2 issues: + +* User-defined isolation level is not applied in PostgreSQL implicit transactions + (a.k.a. auto-commit statements), because no one ``SET SESSION``. +* asyncpg has a bug that ``Connection.transaction(isolation="read_committed")`` always + emit ``BEGIN`` without explicit isolation level, regardless of the actual default + isolation level. + +The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL: + +.. code-block:: python + :emphasize-lines: 13-20,23,24 + + import sqlalchemy as sa + from sqlalchemy import event + from sqlalchemy.dialects.postgresql.base import PGDialect + from sqlalchemy.ext.asyncio import create_async_engine + + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + + def set_isolation_level(dbapi_conn, record): + PGDialect.set_isolation_level( + e.sync_engine.dialect, + dbapi_conn, + "SERIALIZABLE", + ) + + event.listen(e.sync_engine, "connect", set_isolation_level) + + async with e.connect() as conn: + print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL"))) + # Outputs: serializable + + +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ +.. _psycopg2: https://www.psycopg.org/docs/ +.. _asyncpg: https://github.com/MagicStack/asyncpg diff --git a/docs/en/1.1b2/_sources/explanation/why.rst.txt b/docs/en/1.1b2/_sources/explanation/why.rst.txt new file mode 100644 index 0000000..77455b5 --- /dev/null +++ b/docs/en/1.1b2/_sources/explanation/why.rst.txt @@ -0,0 +1,244 @@ +Why Asynchronous ORM? +===================== + +Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly. + + +When asyncio Helps +------------------ + +As Mike Bayer - the author of SQLAlchemy - `pointed out +`__, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM". + +The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +:doc:`async`. So the ultimate reason to use asyncio should be the application itself, +not the database. + +For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead. + +.. image:: ../images/263px-Minimum-Tonne.svg.png + :align: right + +Another example is authentication using `OpenID Connect Authorization Code Flow +`__ as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +`Liebig's law `__, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave. + +Arbitrary delays may happen in the database too. In PostgreSQL, you can |LISTEN|_ to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case. + +In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is: + +.. |LISTEN| replace:: ``LISTEN`` +.. _LISTEN: https://www.postgresql.org/docs/current/sql-listen.html + + +How to Access Database +---------------------- + +The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here. + +Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:: + + import asyncio + from fastapi import FastAPI + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + app = FastAPI() + e = create_engine("postgresql://localhost") + Session = sessionmaker(bind=e) + + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +This follows advices found in :ref:`session_faq_whentocreate` from SQLAlchemy - linking +the **session scope** to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is ``kill -9``. + +What just happened is called a `resource starvation +`__. While the first 10 +requests were waiting for the async work (``sleep(0.1)``), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default ``max_overflow=10``) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition. + +The root cause of such starvation is making asynchronous calls in a **transaction +scope** created implicitly by the default ``autocommit=False``. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in **transaction scopes** by explicitly ending them:: + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + session.rollback() # return the connection to avoid starvation + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +Because of the implicit nature of typical ORMs, it's very hard to identify all such +**transaction scopes** and make sure there's no ``await`` in them - you may have started +an implicit transaction by simply accessing a property like ``current_user.name``. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this. + +What about thread pool? + +The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool. + +This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery_. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design. + +In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs. + +.. _Celery: http://www.celeryproject.org/ + + +Asynchronous ORM Done Right +--------------------------- + +I would describe a proper asynchronous ORM as follows: + + +Don't Starve. +^^^^^^^^^^^^^ + +The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything ``async``. + +In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:: + + @app.get("/") + async def read_root(): + async with db.acquire() as conn: # } + async with conn.begin(): # } These two won't block the main thread + now = await db.scalar("SELECT now()") + await asyncio.sleep(0.1) # do some async work + return str(now) + +Even though this code won't cause any resource starvation, using ``await`` within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages: + +1. As the database connection pool is usually much smaller than the asynchronous server + concurrency, this kind of code caps the concurrency down to the DB pool level. +2. Taking a database connection from the pool for nothing is a waste of resource, + especially when the database pool is the shortest stave. +3. Database transactions should be kept short as much as possible. Because long-hanging + transactions may keep certain database locks, leading to performance issues or even + triggering a chain reaction of deadlocks. + + +Be explicit. +^^^^^^^^^^^^ + +With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an ``await`` or ``async with``, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following `the pattern of Twisted +`__, asyncio is already forcing +explicit ``await`` for any asynchronous operations. + +Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example: + +* We could design the ORM model instances to be stateless, so that the users don't have + to learn and worry about maintaining the state of the instances. +* There shouldn't be any "buffered operations" which users could "flush" with a single + statement once for all. +* The user should just give direct one-off commands and the ORM executes them right away. +* Also I think the convenience tooling should be well-balanced, the user doesn't have to + guess or remember what an API means - for example, there're more than one ways to load + a many-to-one relationship, I'd prefer to write the query by myself rather than trying + to remember what "join_without_n_plus_1()" means. + + +Be productive. +^^^^^^^^^^^^^^ + +Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two? + +I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design. + +Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again. diff --git a/docs/en/1.1b2/_sources/how-to.rst.txt b/docs/en/1.1b2/_sources/how-to.rst.txt new file mode 100644 index 0000000..00a13df --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to.rst.txt @@ -0,0 +1,7 @@ +How-to Guides +============= + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/en/1.1b2/_sources/how-to/alembic.rst.txt b/docs/en/1.1b2/_sources/how-to/alembic.rst.txt new file mode 100644 index 0000000..aa60714 --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/alembic.rst.txt @@ -0,0 +1,135 @@ +Use Alembic +=========== + +Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO. + +To add migrations to project first of all, add alembic as dependency: + +.. code-block:: console + + $ pip install --user alembic + +When you need to set up alembic for your project. + +Prepare sample project. We will have a structure: + + +.. code-block:: console + + alembic_sample/ + my_app/ + models.py + +Inside ``models.py`` define simple DB Model with GINO: + +.. code-block:: python + + from gino import Gino + + db = Gino() + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + +Set up Alembic +^^^^^^^^^^^^^^ + +This will need to be done only once. Go to the main folder of your project +``alembic_sample`` and run: + +.. code-block:: console + + $ alembic init alembic + + +Alembic will create a bunch of files and folders in your project directory. One of them +will be ``alembic.ini``. Open ``alembic.ini`` (you can find it in the main project +folder ``alembic_sample``). Now change property ``sqlalchemy.url =`` with your DB +credentials. Like this: + +.. code-block:: ini + + sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}} + + +Next go to folder ``alembic/`` and open ``env.py`` file. Inside the ``env.py`` file you +need to import the ``db`` object. In our case ``db`` object is ``db`` from ``models`` +modules. This is a variable that links to your ``Gino()`` instance. + +Inside ``alembic/env.py``:: + + from main_app.models import db + + +And change ``target_metadata =`` to:: + + target_metadata = db + +That’s it. We finished setting up Alembic for a project. + +.. note:: + + All ``alembic`` commands must be run always from the folder that contains the + ``alembic.ini`` file. + + +Create first migration revision +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema. + +.. code-block:: console + + $ alembic revision -m "first migration" --autogenerate --head head + +If you have any problems relative to package imports similar to this example: + +.. code-block:: console + + File "alembic/env.py", line 7, in + from main_app.models import db + ModuleNotFoundError: No module named 'main_app' + +Either install your project locally with ``pip install -e .``, ``poetry install`` or +``python setup.py develop``, or add you package to PYTHONPATH, like this: + +.. code-block:: console + + $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample + +After the successful run of ``alembic revision`` in folder ``alembic/versions`` you will +see a file with new migration. + + +Apply migration on DB +^^^^^^^^^^^^^^^^^^^^^ + +Now time to apply migration to DB. It will create tables based on you DB Models. + +.. code-block:: console + + $ alembic upgrade head + +Great. Now you apply your first migration. Congratulations! + +Next time, when you will make any changes in DB models just do: + +.. code-block:: console + + $ alembic revision -m "your migration description" --autogenerate --head head + +And + +.. code-block:: console + + alembic upgrade head + + +Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org diff --git a/docs/en/1.1b2/_sources/how-to/bakery.rst.txt b/docs/en/1.1b2/_sources/how-to/bakery.rst.txt new file mode 100644 index 0000000..2f30075 --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/bakery.rst.txt @@ -0,0 +1,214 @@ +Bake Queries +============ + +.. versionadded:: 1.1 + +Baked queries are used to boost execution performance for constantly-used queries. +Similar to the :doc:`orm/extensions/baked` in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to **bake them before creating the engine**. + +GINO provides two approaches for baked queries: + +1. Low-level :class:`~gino.bakery.Bakery` API +2. High-level :meth:`Gino.bake() ` integration + + +Use Bakery with Bare Engine +--------------------------- + +First, we need a bakery:: + + import gino + + bakery = gino.Bakery() + +Then, let's bake some queries:: + + db_time = bakery.bake("SELECT now()") + +Or queries with parameters:: + + user_query = bakery.bake("SELECT * FROM users WHERE id = :uid") + +Let's assume we have this ``users`` table defined in SQLAlchemy Core:: + + import sqlalchemy as sa + + metadata = sa.MetaData() + user_table = sa.Table( + "users", metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("name", sa.String), + ) + +Now we can bake a similar query with SQLAlchemy Core:: + + user_query = bakery.bake( + sa.select([user_table]).where(user.c.id == sa.bindparam("uid")) + ) + +These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:: + + engine = await gino.create_engine("postgresql://localhost/", bakery=bakery) + +By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene. + +To execute the baked queries, you could treat the :class:`~gino.bakery.BakedQuery` +instances as if they are the queries themselves, for example:: + + now = await engine.scalar(db_time) + +Pass in parameter values:: + + row = await engine.first(user_query, uid=123) + + +Use the :class:`~gino.api.Gino` Integration +-------------------------------------------- + +In a more common scenario, there will be a :class:`~gino.api.Gino` instance, which has +usually a ``bind`` set - either explicitly or by the Web framework extensions:: + + from gino import Gino + + db = Gino() + + async def main(): + async with db.with_bind("postgresql://localhost/"): + ... + +A :class:`~gino.bakery.Bakery` is automatically created in the ``db`` instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + db_time = db.bake("SELECT now()") + user_getter = db.bake(User.query.where(User.id == db.bindparam("uid"))) + +And the execution is also simplified with the same ``bind`` magic:: + + async def main(): + async with db.with_bind("postgresql://localhost/"): + print(await db_time.scalar()) + + user: User = await user_getter.first(uid=1) + print(user.name) + +To make things easier, you could even define the baked queries directly on the +model:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + @db.bake + def getter(cls): + return cls.query.where(cls.id == db.bindparam("uid")) + + @classmethod + async def get(cls, uid): + return await cls.getter.one_or_none(uid=uid) + +Here GINO treats the ``getter()`` as a :meth:`~gino.declarative.declared_attr` with +``with_table=True``, therefore it takes one positional argument ``cls`` for the ``User`` +class. + + +How to customize loaders? +------------------------- + +If possible, you could bake the additional execution options into the query:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + ) + +The :meth:`~gino.bakery.Bakery.bake` method accepts keyword arguments as execution +options to e.g. simplify the example above into:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")), + loader=User.load(comment="Added by loader."), + ) + +If the query construction is complex, :meth:`~gino.bakery.Bakery.bake` could also be +used as a decorator:: + + @db.bake + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + +Or with short execution options:: + + @db.bake(loader=User.load(comment="Added by loader.")) + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")) + +Meanwhile, it is also possible to override the loader at runtime:: + + user: User = await user_getter.load(User).first(uid=1) + print(user.name) # no more comment on user! + +.. hint:: + + This override won't affect the baked query - it's used only in this execution. + + +What APIs are available on :class:`~gino.bakery.BakedQuery`? +------------------------------------------------------------ + +:class:`~gino.bakery.BakedQuery` is a :class:`~gino.api.GinoExecutor`, so it inherited +all the APIs like :meth:`~gino.api.GinoExecutor.all`, +:meth:`~gino.api.GinoExecutor.first`, :meth:`~gino.api.GinoExecutor.one`, +:meth:`~gino.api.GinoExecutor.one_or_none`, :meth:`~gino.api.GinoExecutor.scalar`, +:meth:`~gino.api.GinoExecutor.status`, :meth:`~gino.api.GinoExecutor.load`, +:meth:`~gino.api.GinoExecutor.timeout`, etc. + +:class:`~gino.api.GinoExecutor` is actually the chained ``.gino`` helper API seen +usually in queries like this:: + + user = await User.query.where(User.id == 123).gino.first() + +So a :class:`~gino.bakery.BakedQuery` can be seen as a normal query with the ``.gino`` +suffix, plus it is directly executable. + +.. seealso:: + + Please see API document of :mod:`gino.bakery` for more information. + + +I don't want the prepared statements. +------------------------------------- + +If you don't need all the baked queries (``m``) to create prepared statements for all +the active database connections (``n``) in the beginning, you could set +``prebake=False`` in the engine initialization to prevent the default initial +``m x n`` prepare calls:: + + e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False) + +Or if you're using bind:: + + await db.set_bind("postgresql://...", prebake=False) + +This is useful when you're depending on ``db.gino.create_all()`` to create the tables, +because the prepared statements can only be created after the table creation. + +The prepared statements will then be created and cached lazily on demand. + diff --git a/docs/en/1.1b2/_sources/how-to/contributing.rst.txt b/docs/en/1.1b2/_sources/how-to/contributing.rst.txt new file mode 100644 index 0000000..cbab989 --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/contributing.rst.txt @@ -0,0 +1,152 @@ +.. highlight:: console + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/python-gino/gino/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `gino` for local development. + +1. Fork the `gino` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/gino.git + +3. Create a branch for local development:: + + $ cd gino/ + $ git checkout -b name-of-your-bugfix-or-feature + +Now you can make your changes locally. + +4. Create virtual environment. Example for virtualenvwrapper:: + + $ mkvirtualenv gino + +5. Activate the environment and install requirements:: + + $ pip install -r requirements_dev.txt + +6. When you're done making changes, check that your changes pass syntax checks:: + + $ flake8 gino tests + +7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):: + + $ pytest tests + $ tox + +8. For docs run:: + + $ make docs + +It will build and open up docs in your browser. + +9. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +10. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check + https://github.com/python-gino/gino/actions?query=event%3Apull_request + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test -svx tests.test_gino + +By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:: + + CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino'; + CREATE DATABASE gino WITH OWNER = gino; + +Then run the tests like so:: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino + $ py.test + +Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):: + + $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem + $ openssl rsa -in privkey.pem -passin pass:abcd -out server.key + $ openssl req -x509 -in server.req -text -key server.key -out server.crt + $ chmod 600 server.key + $ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + +Terminal 2 (client):: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433 + $ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'" + $ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;" + $ pytest tests/test_aiohttp.py diff --git a/docs/en/1.1b2/_sources/how-to/crud.rst.txt b/docs/en/1.1b2/_sources/how-to/crud.rst.txt new file mode 100644 index 0000000..dd23336 --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/crud.rst.txt @@ -0,0 +1,5 @@ +==== +CRUD +==== + +**THIS IS A WIP** diff --git a/docs/en/1.1b2/_sources/how-to/faq.rst.txt b/docs/en/1.1b2/_sources/how-to/faq.rst.txt new file mode 100644 index 0000000..73c340a --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/faq.rst.txt @@ -0,0 +1,389 @@ +Frequently Asked Questions +========================== + +SQLAlchemy 1.4 supports asyncio, what will GINO be? +--------------------------------------------------- + +Starting from 1.4, SQLAlchemy will `support asyncio +`__. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +`greenlet `__. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed. + +Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features: + +* Contextual Connections (see :doc:`../explanation/engine`) +* SQLAlchemy Core-based CRUD models +* The GINO Loader system +* Async MySQL support +* Typing support :sup:`NEW` +* `Trio `__ support :sup:`NEW` +* Execution performance :sup:`NEW` + +As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized. + + +ORM or not ORM? +--------------- + +GINO does perform the Object-Relational Mapping work under the +`Data Mapper Pattern `_, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely `non-ORM way +`__. + + +Can I use features of SQLAlchemy ORM? +------------------------------------- + +SQLAlchemy has `two parts `__: + +* SQLAlchemy Core +* SQLAlchemy ORM + +GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO. + + +How to join? +------------ + +GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know `how to write join in +SQLAlchemy `_. +Especially, `Ádám `_ made some amazing upgrades in +GINO `#113 `_ to make join easier, so +that you can use model classes directly as if they are tables in joining:: + + results = await User.join(Book).select().gino.all() + + +How to connect to database through SSL? +--------------------------------------- + +It depends on the dialect and database driver. For asyncpg, keyword arguments +on :func:`asyncpg.connect() ` are directly +available on :func:`~gino.create_engine` or :meth:`db.set_bind() +`. Therefore, enabling SSL is rather easy:: + + engine = await gino.create_engine(..., ssl=True) + + +What is aiocontextvars and what does it do? +------------------------------------------- + +It is a partial backport of the new built-in module `contextvars +`_ introduced in Python +3.7. In Python 3.5 and 3.6, ``aiocontextvars`` patches ``loop.create_task()`` +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2 + +If you are using Python 3.7, then ``aiocontextvars`` does nothing at all. + +.. note:: + + This answer is for GINO 0.8 and later, please check earlier versions of + this documentation if you are using GINO 0.7. + + +How to define relationships? +---------------------------- + +GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see :doc:`loaders` for more information. + + +How to define index with multiple columns? +------------------------------------------ + +:: + + class User(db.Model): + __tablename__ = 'users' + + first_name = db.Column(db.Unicode()) + last_name = db.Column(db.Unicode()) + + _name_idx = db.Index('index_on_name', 'first_name', 'last_name') + +The ``_name_idx`` is not used. + + +Is there a django admin interface for GINO? +------------------------------------------- + +Not quite yet, please follow `this discussion +`__. + + +How to use multiple databases for different users on the fly? +------------------------------------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. + +In order to use multiple databases, you would need multiple +:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with +lazy engine creation:: + + from asyncio import Future + from contextvars import ContextVar + + from fastapi import FastAPI, Request + from gino import create_engine + from gino.ext.starlette import Gino + + engines = {} + dbname = ContextVar("dbname") + + + class ContextualGino(Gino): + @property + def bind(self): + e = engines.get(dbname.get("")) + if e and e.done(): + return e.result() + else: + return self._bind + + @bind.setter + def bind(self, val): + self._bind = val + + + app = FastAPI() + db = ContextualGino(app) + + + @app.middleware("http") + async def lazy_engines(request: Request, call_next): + name = request.query_params.get("db", "postgres") + fut = engines.get(name) + if fut is None: + fut = engines[name] = Future() + try: + engine = await create_engine("postgresql://localhost/" + name) + except Exception as e: + fut.set_exception(e) + del engines[name] + raise + else: + fut.set_result(engine) + await fut + dbname.set(name) + return await call_next(request) + + + @app.get("/") + async def get(): + return dict(dbname=await db.scalar("SELECT current_database()")) + + +How to load complex query results? +---------------------------------- + +The API doc of :mod:`gino.loader` explains the available loaders, and there're a few +examples in :doc:`loaders` too. + +Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders - +:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Be ware of the :class:`tuple` in ``.gino.load((...))``. + + + +How to do bulk or batch insert / update? +----------------------------------------- + +For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:: + + new_names = ["Austin", "Ali", "Jeff", "Marissa"] + +To quickly insert the names in one query, first construct a dict with the +``{"model_key": "value"}`` format:: + + new_names_dict = [dict(name=new_name) for new_name in new_names] + >> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}] + +Finally, run an insert statement on the model:: + + await User.insert().gino.all(new_names_dict) + + +How to print the executed SQL? +------------------------------ + +GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``. +(Or ``db.set_bind(..., echo=True)``) Please see also `here +`__. + +If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`. + + +How to run ``EXISTS`` SQL? +-------------------------- + +:: + + await db.scalar(db.exists().where(User.email == email).select()) + + +How to work with Alembic? +------------------------- + +The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the +key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py`` +will do. See :doc:`alembic` for more details. + + +How to join the same table twice? +--------------------------------- + +This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +:func:`~gino.crud.CRUDModel.alias`, for example:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.ForeignKey("users.id")) + + Parent = User.alias() + query = User.outerjoin(Parent, User.parent_id == Parent.id).select() + users = await query.gino.load(User.load(parent=Parent)).all() + + +.. _raw-sql: + +How to execute raw SQL with parameters? +--------------------------------------- + +Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments:: + + query = db.text('SELECT * FROM users WHERE id = :id_val') + row = await db.first(query, id_val=1) + +You may even load the rows into model instances:: + + query = query.execution_options(loader=User) + user = await db.first(query, id_val=1) + + +Gino engine is not initialized? +------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before +this, you'll see this error: + +.. code-block:: text + + gino.exceptions.UninitializedError: Gino engine is not initialized. + +You could use either: + +* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the + bind on the :class:`~gino.api.Gino` instance. +* Use one of the Web framework extensions to set the bind for you in usually the server + start-up hook. +* Use explicit ``bind`` for each execution, for example:: + + engine = await create_engine("...") + # ... + user = await User.get(request.user_id, bind=engine) + + +How can I do SQL xxxx in GINO? +------------------------------ + +GINO uses `SQLAlchemy Core `__ queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column +attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column` +instances, you can use them in building your SQLAlchemy Core queries. + +Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above. diff --git a/docs/en/1.1b2/_sources/how-to/json-props.rst.txt b/docs/en/1.1b2/_sources/how-to/json-props.rst.txt new file mode 100644 index 0000000..45cf95d --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/json-props.rst.txt @@ -0,0 +1,157 @@ +JSON Property +============= + +GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields. + +Quick Start +----------- + +:: + + from gino import Gino + from sqlalchemy.dialects.postgresql import JSONB + + db = Gino() + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + birthday = db.DateTimeProperty() + +The ``age`` and ``birthday`` are JSON properties stored in the ``profile`` column. You +may use them the same way as a normal GINO model field:: + + u = await User.create(name="daisy", age=18) + print(u.name, u.age) # daisy 18 + +.. note:: + + ``profile`` is the default column name for all JSON properties in a model. If you + need a different column name for some JSON properties, you'll need to specify + explicitly:: + + audit_profile = db.Column(JSON, nullable=False, server_default="{}") + + access_log = db.ArrayProperty(prop_name="audit_profile") + abnormal_detected = db.BooleanProperty(prop_name="audit_profile") + +Using JSON properties in queries is supported:: + + await User.query.where(User.age > 16).gino.all() + +This is simply translated into a native JSON query like this: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16) + +Datetime type is very much the same:: + + from datetime import datetime + + await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all() + +And the generated SQL: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2 + -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0)) + +Here's a list of all the supported JSON properties: + ++----------------------------+-----------------------------+-------------+---------------+ +| JSON Property | Python type | JSON type | Database Type | ++============================+=============================+=============+===============+ +| :class:`.StringProperty` | :class:`str` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.IntegerProperty` | :class:`int` | ``number`` | ``int`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.BooleanProperty` | :class:`bool` | ``boolean`` | ``boolean`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.DateTimeProperty` | :class:`~datetime.datetime` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ObjectProperty` | :class:`dict` | ``object`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ArrayProperty` | :class:`list` | ``array`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ + + +Hooks +----- + +JSON property provides 2 instance-level hooks to customize the data:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @age.before_set + def age(self, val): + return val - 1 + + @age.after_get + def age(self, val): + return val + 1 + + u = await User.create(name="daisy", age=18) + print(u.name, u.profile, u.age) # daisy {'age': 17} 18 + +And 1 class-level hook to customize the SQLAlchemy expression of the property:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + height = db.JSONProperty() + + @height.expression + def height(cls, exp): + return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT) + + +Create Index on JSON Properties +------------------------------- + +We'll need to use :meth:`~gino.declarative.declared_attr` to wait until the model class +is initialized. The rest is very much the same as defining a usual index:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @db.declared_attr + def age_idx(cls): + return db.Index("age_idx", cls.age) + +This will lead to the SQL below executed if you run ``db.gino.create_all()``: + +.. code-block:: plpgsql + + CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER)); + +.. warning:: + + Alembic doesn't support auto-generating revisions for functional indexes yet. You'll + need to manually edit the revision. Please follow `this issue + `__ for updates. diff --git a/docs/en/1.1b2/_sources/how-to/loaders.rst.txt b/docs/en/1.1b2/_sources/how-to/loaders.rst.txt new file mode 100644 index 0000000..6d82937 --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/loaders.rst.txt @@ -0,0 +1,461 @@ +======================== +Loaders and Relationship +======================== + +Loaders are used to load database row results into objects. + +GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined. + + +Model Loader +------------ + +The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:: + + query = db.select([User]) + rows = await query.gino.all() + +In order to load rows into ``User`` objects, you can provide an execution +option ``loader`` with a new :class:`~gino.loader.ModelLoader` instance:: + + from gino.loader import ModelLoader + + query = db.select([User]) + query = query.execution_options(loader=ModelLoader(User)) + users = await query.gino.all() + +The :class:`~gino.loader.ModelLoader` would then load each database row into a +``User`` object. As this is frequently used, GINO made it a shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User.load()) + users = await query.gino.all() + +And another shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User) + users = await query.gino.all() + +.. tip:: + + ``User`` as ``loader`` is transformed into ``ModelLoader(User)`` by + :meth:`Loader.get() `, explained later in "Loader + Expression". + +And again:: + + query = db.select([User]) + users = await query.gino.load(User).all() + +This is identical to the normal CRUD query:: + + users = await User.query.gino.all() + + +Loader Expression +----------------- + +So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than :class:`~gino.loader.ModelLoader`, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders. + +Here is an example using all loaders at once:: + + uid, user, sep, cols = await db.select([User]).gino.load( + ( + User.id, + User, + '|', + lambda row, ctx: len(row), + ) + ).first() + +Let's check this piece by piece. Overall, the argument of +:meth:`~gino.api.GinoExecutor.load` is a tuple. This is interpreted into a +:class:`~gino.loader.TupleLoader`, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a :class:`~gino.loader.TupleLoader` is a tuple. + +:class:`~sqlalchemy.schema.Column` in Loader Expressions are interpreted as +:class:`~gino.loader.ColumnLoader`. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, :class:`~gino.loader.ColumnLoader` uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use :class:`~gino.loader.ColumnLoader`, +you'll have to declare columns for the query:: + + now = db.Column('time', db.DateTime()) + result = await db.first(db.text( + 'SELECT now() AT TIME ZONE \'UTC\'' + ).columns( + now, + ).gino.load( + ('now:', now) + ).query) + print(*result) # now: 2018-04-08 08:23:02.431847 + +Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +:class:`~gino.loader.ModelLoader`. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values. + +.. tip:: + + For a complex loader expression, the same row is given to all loaders, so + it doesn't matter ``User.id`` is already used before the model loader. + +The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +:func:`map`, the return value of the call will be the loaded result. + +At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the ``'|'`` separator, it will show up as the third item +in every result returned by the query. + + +Many-to-One Relationship +------------------------ + +A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + +So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:: + + async for child in Child.load(parent=Parent).gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +As you may have noticed, ``Child.load`` is exactly the shortcut to create +:class:`~gino.loader.ModelLoader` in the very first example. With some +additional keyword arguments, ``Child.load(parent=Parent)`` is still a +:class:`~gino.loader.ModelLoader` for ``Child``, the model loader is at the +same time a **query builder**. It is identical to do this:: + + async for child in Child.load(parent=Parent).query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +The :attr:`~gino.loader.Loader.query` dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The :class:`~gino.loader.Loader` simply forwarded unknown +attributes to its :attr:`~gino.loader.Loader.query`, that's why ``.query`` can +be omitted. + +For :class:`~gino.loader.ModelLoader`, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using :func:`setattr`. For example, ``Parent`` is +interpreted as ``ModelLoader(Parent)`` which loads ``Parent`` instances, and +``Parent`` instances are set as the ``parent`` attribute of the outer ``Child`` +instance. + +.. warning:: + + If multiple children references the same parent, then each child owns a + unique parent instance with identical values. + +.. tip:: + + You don't have to define ``parent`` attribute on ``Child``. But if you do, + you gain the ability to customize how parent is stored or retrieved. For + example, let's store the parent instance as ``_parent``:: + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + _parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + +The query builder works recursively. For :class:`~gino.loader.ModelLoader`, it +uses ``LEFT OUTER JOIN`` to connect the ``FROM`` clauses, in order to achieve +many-to-one scenario. The ``ON`` clause is determined automatically by foreign +keys. You can also customize the ``ON`` clause in case there is no foreign key +(a promise is a promise):: + + loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + async for child in loader.query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +And subloaders can be nested:: + + subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id)) + +By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values. + + +Self Referencing +---------------- + +.. warning:: + + Experimental feature. + +Self referencing is usually used to create a tree-like structure. For example:: + + class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('categories.id')) + +In order to load leaf categories with their parents, an alias is needed:: + + Parent = Category.alias() + +Then the query would be something like this:: + + parents = db.select([Category.parent_id]) + query = Category.load(parent=Parent.on( + Category.parent_id == Parent.id + )).where( + ~Category.id.in_(db.select([Category.alias().parent_id])) + ) + async for c in query.gino.iterate(): + print(f'Leaf: {c.id}, Parent: {c.parent.id}') + +The generated SQL looks like this: + +.. code-block:: SQL + + SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id + FROM categories LEFT OUTER JOIN categories AS categories_1 + ON categories.parent_id = categories_1.id + WHERE categories.id NOT IN ( + SELECT categories_2.parent_id + FROM categories AS categories_2 + ) + + +Other Relationships +------------------- + +GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + @children.setter + def add_child(self, child): + self._children.add(child) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child)).all() + +Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the ``add_child`` setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +``parent.add_child = new_child``. + +Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries. + +GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._child = None + + @property + def child(self): + return self._child + + @child.setter + def child(self, child): + self._child = child + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all() + + +Similarly, you can build many-to-many relationships in the same way:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + def add_child(self, child): + self._children.add(child) + child._parents.add(self) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._parents = set() + + @property + def parents(self): + return self._parents + + + class ParentXChild(db.Model): + __tablename__ = 'parents_x_children' + + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + child_id = db.Column(db.Integer, db.ForeignKey('children.id')) + + + query = Parent.outerjoin(ParentXChild).outerjoin(Child).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all() + +Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ``ParentXChild`` instances. + + +Advanced Usage of Loaders +------------------------- + +You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For `example +`_, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Using alias to get ID-ascending pairs from the same table:: + + ua1 = User.alias() + ua2 = User.alias() + join_query = select([ua1, ua2]).where(ua1.id < ua2.id) + loader = ua1.load('id'), ua2.load('id') + result = await join_query.gino.load(loader).all() + print(result) # e.g. [(1, 2), (1, 3), (2, 3)] + +Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future. diff --git a/docs/en/1.1b2/_sources/how-to/pool.rst.txt b/docs/en/1.1b2/_sources/how-to/pool.rst.txt new file mode 100644 index 0000000..4574ab1 --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/pool.rst.txt @@ -0,0 +1,27 @@ +=============== +Connection Pool +=============== + +Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +:class:`~gino.dialects.asyncpg.NullPool`), and users can define their own pools. +The base class should be :class:`~gino.dialects.base.Pool`. + +To use non-default pools in raw GINO:: + + from gino.dialects.asyncpg import NullPool + create_engine('postgresql://...', pool_class=NullPool) + +To use non-default pools in extensions (taking Sanic as an example):: + + from gino.dialects.asyncpg import NullPool + from gino.ext.sanic import Gino + + app = sanic.Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_KWARGS = dict( + pool_class=NullPool, + ) + db = Gino() + db.init_app(app) diff --git a/docs/en/1.1b2/_sources/how-to/schema.rst.txt b/docs/en/1.1b2/_sources/how-to/schema.rst.txt new file mode 100644 index 0000000..c57eaaa --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/schema.rst.txt @@ -0,0 +1,232 @@ +================== +Schema Declaration +================== + +There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy :class:`~sqlalchemy.schema.Table`. + + +GINO Engine +----------- + +This is the minimized way to use GINO - using only +:class:`~gino.engine.GinoEngine` (and :class:`~gino.engine.GinoConnection` +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two. + +For example, the table declaration is the same as SQLAlchemy core `tutorial +`_:: + + from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey + + metadata = MetaData() + + users = Table( + 'users', metadata, + + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + ) + + addresses = Table( + 'addresses', metadata, + + Column('id', Integer, primary_key=True), + Column('user_id', None, ForeignKey('users.id')), + Column('email_address', String, nullable=False) + ) + +.. note:: + + When using GINO Engine only, it is usually your own business to create the + tables with either :meth:`~sqlalchemy.schema.MetaData.create_all` on a + normal non-async SQLAlchemy engine, or using Alembic. However it is still + possible to be done with GINO if it had to:: + + import gino + from gino.schema import GinoSchemaVisitor + + async def main(): + engine = await gino.create_engine('postgresql://...') + await GinoSchemaVisitor(metadata).create_all(engine) + +Then, construct queries, in SQLAlchemy core too:: + + ins = users.insert().values(name='jack', fullname='Jack Jones') + +So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:: + + async def main(): + engine = await gino.create_engine('postgresql://localhost/gino') + conn = await engine.acquire() + await conn.status(ins) + print(await conn.all(users.select())) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Here :func:`~gino.create_engine` creates a :class:`~gino.engine.GinoEngine`, +then :meth:`~gino.engine.GinoEngine.acquire` checks out a +:class:`~gino.engine.GinoConnection`, and +:meth:`~gino.engine.GinoConnection.status` executes the insert and returns the +status text. This works similarly as SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execute` - they take the same parameters +but return a bit differently. There are also other similar query APIs: + +* :meth:`~gino.engine.GinoConnection.all` returns a list of + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.first` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.one` returns one + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.one_or_none` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.scalar` returns a single value, or + ``None`` +* :meth:`~gino.engine.GinoConnection.iterate` returns an asynchronous iterator + which yields :class:`~sqlalchemy.engine.RowProxy` + +Please go to their API for more information. + + +GINO Core +--------- + +In previous scenario, :class:`~gino.engine.GinoEngine` must not be set to +:attr:`metadata.bind ` because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of :class:`~sqlalchemy.schema.MetaData` as :class:`~gino.api.Gino`, +usually instantiated globally under the name of ``db``. It can be used as a +normal :class:`~sqlalchemy.schema.MetaData` still offering some conveniences: + +* It delegates most public types you can access on ``sqlalchemy`` +* It works with both normal SQLAlchemy engine and asynchronous GINO engine +* It exposes all query APIs on :class:`~gino.engine.GinoConnection` level +* It injects two ``gino`` extensions on SQLAlchemy query clauses and schema + items, allowing short inline execution like ``users.select().gino.all()`` +* It is also the entry for the third scenario, see later + +Then we can achieve previous scenario with less code like this:: + + from gino import Gino + + db = Gino() + + users = db.Table( + 'users', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('name', db.String), + db.Column('fullname', db.String), + ) + + addresses = db.Table( + 'addresses', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('user_id', None, db.ForeignKey('users.id')), + db.Column('email_address', db.String, nullable=False) + ) + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await users.insert().values( + name='jack', + fullname='Jack Jones', + ).gino.status() + print(await users.select().gino.all()) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but ``sqlalchemy`` seems +never imported. This is useful when ORM is unwanted. + +.. tip:: + + `asyncpgsa `_ does the same thing, + but in a conceptually reversed way - instead of having asyncpg work for + SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that + way too because GINO is inspired by asyncpgsa). Either way works fine, it's + just a matter of taste of whose API style to use, SQLAlchemy or asyncpg. + + +GINO ORM +-------- + +If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:: + + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + fullname = db.Column(db.String) + + + class Address(db.Model): + __tablename__ = 'addresses' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(None, db.ForeignKey('users.id')) + email_address = db.Column(db.String, nullable=False) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await User.create(name='jack', fullname='Jack Jones') + print(await User.query.gino.all()) + # Outputs: [] + +.. important:: + + The ``__tablename__`` is a mandatory field to define a concrete model. + +As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in ``db``. The ``class`` style is just +more declarative. Instead of ``users.c.name``, you can now access the column by +``User.name``. The implicitly created :class:`~sqlalchemy.schema.Table` is +available at ``User.__table__`` and ``Address.__table__``. You can use anything +that works in GINO core here. + +.. note:: + + Column names can be different as a class property and database column. + For example, name can be declared as + ``nickname = db.Column('name', db.Unicode(), default='noname')``. In this + example, ``User.nickname`` is used to access the column, while in database, + the column name is ``name``. + + What's worth mentioning is where raw SQL statements are used, or + ``TableClause`` is involved, like ``User.insert()``, the original name is + required to be used, because in this case, GINO has no knowledge about the + mappings. + +.. tip:: + + ``db.Model`` is a dynamically created parent class for your models. It is + associated with the ``db`` on initialization, therefore the table is put in + the very ``db`` when you declare your model class. + +Things become different when it comes to CRUD. You can use model level methods +to directly :meth:`~gino.crud.CRUDModel.create` a model instance, instead of +inserting a new row. Or :meth:`~gino.crud.CRUDModel.delete` a model instance +without needing to specify the where clause manually. Query returns model +instances instead of :class:`~sqlalchemy.engine.RowProxy`, and row values are +directly available as attributes on model instances. See also: +:doc:`/how-to/crud`. + +After all, :class:`~gino.engine.GinoEngine` is always in use. Next let's dig +more into it. diff --git a/docs/en/1.1b2/_sources/how-to/transaction.rst.txt b/docs/en/1.1b2/_sources/how-to/transaction.rst.txt new file mode 100644 index 0000000..558a5ae --- /dev/null +++ b/docs/en/1.1b2/_sources/how-to/transaction.rst.txt @@ -0,0 +1,116 @@ +=========== +Transaction +=========== + +It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an ``await`` will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it. + + +Basic usage +----------- + +Transactions belong to :class:`~gino.engine.GinoConnection`. The most common +way to use transactions is through an ``async with`` statement:: + + async with connection.transaction() as tx: + await connection.status('INSERT INTO mytable VALUES(1, 2, 3)') + +This guarantees a transaction is opened when entering the ``async with`` block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at :attr:`~gino.transaction.GinoTransaction.raw_transaction`, but in +most cases you don't need to touch it. + +GINO provides two convenient shortcuts to end the transaction early: + +* :meth:`tx.raise_commit() ` +* :meth:`tx.raise_rollback() ` + +They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the ``async with`` block after +:meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` is skipped. The +internal exception is inherited from :exc:`BaseException` so that normal ``try +... except Exception`` block can't trap it. This exception stops propagating at +the end of ``async with`` block, so you don't need to worry about handling it. + +Transactions can also be started on a :class:`~gino.engine.GinoEngine`:: + + async with engine.transaction() as tx: + await engine.status('INSERT INTO mytable VALUES(1, 2, 3)') + +Here a :class:`~gino.engine.GinoConnection` is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The :class:`~gino.engine.GinoConnection` instance is accessible at +:attr:`tx.connection `. Other than +that, everything else is the same. + +.. important:: + + The implicit connection is by default borrowed with ``reuse=True``. That + means using :meth:`~gino.engine.GinoEngine.transaction` of + :class:`~gino.engine.GinoEngine` within a connection context is the same as + calling :meth:`~gino.engine.GinoConnection.transaction` of the current + connection without having to reference it, no separate connection shall be + created. + +Similarly, if your :class:`~gino.api.Gino` instance has a bind, you may also do +the same on it:: + + async with db.transaction() as tx: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + + +Nested Transactions +------------------- + +Transactions can be nested, nested transaction will create a `savepoint +`_ as for +now on asyncpg. A similar example from asyncpg doc:: + + async with connection.transaction() as tx1: + await connection.status('CREATE TABLE mytab (a int)') + + # Create a nested transaction: + async with connection.transaction() as tx2: + await connection.status('INSERT INTO mytab (a) VALUES (1), (2)') + # Rollback the nested transaction: + tx2.raise_rollback() + + # Because the nested transaction was rolled back, there + # will be nothing in `mytab`. + assert await connection.all('SELECT a FROM mytab') == [] + +As you can see, the :meth:`~gino.transaction.GinoTransaction.raise_rollback` +breaks only the ``async with`` block of the specified ``tx2``, the outer +transaction ``tx1`` just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate. + +Because of the default reusing behavior, transactions on engine or ``db`` +follows the same nesting rules. Please see +:class:`~gino.transactions.GinoTransaction` for more information. + + +Manual Control +-------------- + +Other than using ``async with``, you can also manually control the +transaction:: + + tx = await connection.transaction() + try: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + await tx.commit() + except Exception: + await tx.rollback() + raise + +You can't use :meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` here, similarly it is +prohibited to use :meth:`~gino.transaction.GinoTransaction.commit` and +:meth:`~gino.transaction.GinoTransaction.rollback` in an ``async with`` block. diff --git a/docs/en/1.1b2/_sources/index.rst.txt b/docs/en/1.1b2/_sources/index.rst.txt new file mode 100644 index 0000000..1512377 --- /dev/null +++ b/docs/en/1.1b2/_sources/index.rst.txt @@ -0,0 +1,127 @@ +Welcome to GINO's documentation! +================================ + +.. image:: https://img.shields.io/pypi/v/gino?logo=python&logoColor=white&color=3E6CDE&style=flat-square + :alt: PyPI Release Version + :target: https://pypi.python.org/pypi/gino + +.. image:: https://img.shields.io/github/workflow/status/python-gino/gino/test?label=test&logo=github&color=3E6CDE&style=flat-square + :alt: GitHub Workflow Status for tests + :target: https://github.com/python-gino/gino/actions?query=workflow%3Atest + +.. image:: https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy&color=3E6CDE&style=flat-square + :alt: Codacy coverage + :target: https://app.codacy.com/gh/python-gino/gino/dashboard + +.. image:: https://img.shields.io/badge/Dependabot-active-brightgreen?logo=dependabot&color=3E6CDE&style=flat-square + :target: https://app.dependabot.com/accounts/python-gino/projects/129260 + :alt: Dependabot + + +GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy_ core for Python asyncio_. Now (early 2020) GINO supports only one +dialect asyncpg_. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _asyncpg: https://github.com/MagicStack/asyncpg + + +.. cssclass:: boxed-nav + +* .. image:: images/tutorials.svg + + :doc:`tutorials` + + Lessons for the newcomer to get started + +* .. image:: images/how-to.svg + + :doc:`how-to` + + Solve specific problems by steps + +* .. image:: images/explanation.svg + + :doc:`explanation` + + Explains the background and context + +* .. image:: images/reference.svg + + :doc:`reference` + + Describes the software as it is + + +Useful Links +------------ + +.. cssclass:: boxed-nav + +* .. image:: images/github.svg + + `Source Code `_ + + https://github.com/python-gino/gino + +* .. image:: images/community.svg + + `Community `_ + + https://gitter.im/python-gino/Lobby + +* .. image:: images/open-source.svg + + `BSD license `_ + + GINO is free software + +* .. image:: images/python.svg + + `Download `_ + + Download GINO from PyPI + + +.. cssclass:: divio + +Sections by `Divio `_. + +.. toctree:: + :caption: Tutorials + :maxdepth: 1 + :glob: + :hidden: + + tutorials + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* + +.. toctree:: + :caption: How-to Guides + :maxdepth: 1 + :glob: + :hidden: + + how-to + how-to/* + +.. toctree:: + :caption: Explanation + :maxdepth: 1 + :glob: + :hidden: + + explanation + explanation/* + +.. toctree:: + :caption: Reference + :maxdepth: 1 + :glob: + :hidden: + + reference + reference/* diff --git a/docs/en/1.1b2/_sources/reference.rst.txt b/docs/en/1.1b2/_sources/reference.rst.txt new file mode 100644 index 0000000..977912b --- /dev/null +++ b/docs/en/1.1b2/_sources/reference.rst.txt @@ -0,0 +1,7 @@ +Reference +========= + +.. toctree:: + :glob: + + reference/* diff --git a/docs/en/1.1b2/_sources/reference/api.rst.txt b/docs/en/1.1b2/_sources/reference/api.rst.txt new file mode 100644 index 0000000..23fc548 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api.rst.txt @@ -0,0 +1,6 @@ +API Reference +============= + +.. toctree:: + + api/gino diff --git a/docs/en/1.1b2/_sources/reference/api/gino.aiocontextvars.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.aiocontextvars.rst.txt new file mode 100644 index 0000000..4728456 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.aiocontextvars.rst.txt @@ -0,0 +1,7 @@ +gino.aiocontextvars module +========================== + +.. automodule:: gino.aiocontextvars + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.api.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.api.rst.txt new file mode 100644 index 0000000..8825a74 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.api.rst.txt @@ -0,0 +1,7 @@ +gino.api module +=============== + +.. automodule:: gino.api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.bakery.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.bakery.rst.txt new file mode 100644 index 0000000..5762ab7 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.bakery.rst.txt @@ -0,0 +1,7 @@ +gino.bakery module +================== + +.. automodule:: gino.bakery + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.crud.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.crud.rst.txt new file mode 100644 index 0000000..9683c99 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.crud.rst.txt @@ -0,0 +1,7 @@ +gino.crud module +================ + +.. automodule:: gino.crud + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.declarative.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.declarative.rst.txt new file mode 100644 index 0000000..8ec0bdd --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.declarative.rst.txt @@ -0,0 +1,7 @@ +gino.declarative module +======================= + +.. automodule:: gino.declarative + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.dialects.aiomysql.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.dialects.aiomysql.rst.txt new file mode 100644 index 0000000..338ccc5 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.dialects.aiomysql.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.aiomysql module +============================= + +.. automodule:: gino.dialects.aiomysql + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.dialects.asyncpg.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.dialects.asyncpg.rst.txt new file mode 100644 index 0000000..9423016 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.dialects.asyncpg.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.asyncpg module +============================ + +.. automodule:: gino.dialects.asyncpg + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.dialects.base.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.dialects.base.rst.txt new file mode 100644 index 0000000..c512657 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.dialects.base.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.base module +========================= + +.. automodule:: gino.dialects.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.dialects.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.dialects.rst.txt new file mode 100644 index 0000000..5366ba2 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.dialects.rst.txt @@ -0,0 +1,20 @@ +gino.dialects package +===================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects.aiomysql + gino.dialects.asyncpg + gino.dialects.base + +Module contents +--------------- + +.. automodule:: gino.dialects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.engine.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.engine.rst.txt new file mode 100644 index 0000000..0075c29 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.engine.rst.txt @@ -0,0 +1,7 @@ +gino.engine module +================== + +.. automodule:: gino.engine + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.exceptions.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.exceptions.rst.txt new file mode 100644 index 0000000..d3659da --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.exceptions.rst.txt @@ -0,0 +1,7 @@ +gino.exceptions module +====================== + +.. automodule:: gino.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.ext.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.ext.rst.txt new file mode 100644 index 0000000..fe56088 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.ext.rst.txt @@ -0,0 +1,10 @@ +gino.ext package +================ + +Module contents +--------------- + +.. automodule:: gino.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.json_support.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.json_support.rst.txt new file mode 100644 index 0000000..5a7c069 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.json_support.rst.txt @@ -0,0 +1,7 @@ +gino.json\_support module +========================= + +.. automodule:: gino.json_support + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.loader.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.loader.rst.txt new file mode 100644 index 0000000..06ce463 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.loader.rst.txt @@ -0,0 +1,7 @@ +gino.loader module +================== + +.. automodule:: gino.loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.rst.txt new file mode 100644 index 0000000..b507d15 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.rst.txt @@ -0,0 +1,38 @@ +gino package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects + gino.ext + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.aiocontextvars + gino.api + gino.bakery + gino.crud + gino.declarative + gino.engine + gino.exceptions + gino.json_support + gino.loader + gino.schema + gino.strategies + gino.transaction + +Module contents +--------------- + +.. automodule:: gino + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.schema.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.schema.rst.txt new file mode 100644 index 0000000..36fbb62 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.schema.rst.txt @@ -0,0 +1,7 @@ +gino.schema module +================== + +.. automodule:: gino.schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.strategies.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.strategies.rst.txt new file mode 100644 index 0000000..01de55b --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.strategies.rst.txt @@ -0,0 +1,7 @@ +gino.strategies module +====================== + +.. automodule:: gino.strategies + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/api/gino.transaction.rst.txt b/docs/en/1.1b2/_sources/reference/api/gino.transaction.rst.txt new file mode 100644 index 0000000..2a28e55 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/api/gino.transaction.rst.txt @@ -0,0 +1,7 @@ +gino.transaction module +======================= + +.. automodule:: gino.transaction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/1.1b2/_sources/reference/extensions.rst.txt b/docs/en/1.1b2/_sources/reference/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/en/1.1b2/_sources/reference/extensions/sanic.rst.txt b/docs/en/1.1b2/_sources/reference/extensions/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/extensions/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/en/1.1b2/_sources/reference/extensions/starlette.rst.txt b/docs/en/1.1b2/_sources/reference/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/en/1.1b2/_sources/reference/extensions/tornado.rst.txt b/docs/en/1.1b2/_sources/reference/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/en/1.1b2/_sources/reference/history.rst.txt b/docs/en/1.1b2/_sources/reference/history.rst.txt new file mode 100644 index 0000000..65889ae --- /dev/null +++ b/docs/en/1.1b2/_sources/reference/history.rst.txt @@ -0,0 +1,620 @@ +======= +History +======= + +GINO 1.1 +-------- + +1.1.0 (pending) +^^^^^^^^^^^^^^^ + +* Added baked query feature (#478 #659 #667) +* Added ``Query.gino.execution_options`` shortcut (#659) +* Added ``@db.declared_attr(with_table=True)`` (#659) + + +GINO 1.0 +-------- + +Migrating to GINO 1.0 +^^^^^^^^^^^^^^^^^^^^^ + +GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras: + ++------------------------+---------------------------------+ +| Extension Module | Installation in GINO 1.0 | ++========================+=================================+ +| ``gino.ext.starlette`` | ``pip install gino[starlette]`` | ++------------------------+---------------------------------+ +| ``gino.ext.aiohttp`` | ``pip install gino[aiohttp]`` | ++------------------------+---------------------------------+ +| ``gino.ext.sanic`` | ``pip install gino[sanic]`` | ++------------------------+---------------------------------+ +| ``gino.ext.tornado`` | ``pip install gino[tornado]`` | ++------------------------+---------------------------------+ +| ``gino.ext.quart`` | ``pip install gino[quart]`` | ++------------------------+---------------------------------+ + +The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed ``gino[starlette]``:: + + from gino.ext.starlette import Gino + +GINO 1.0 switched to `Poetry `__ for package and +dependency management and started to use the +`src layout `__. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process: + +* Source files are now located under ``src`` directory. +* The src dist on PyPI does not include tests, docs and some other files due to + a limitation of Poetry. + +1.0.1 (2020-06-08) +^^^^^^^^^^^^^^^^^^ + +* Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4) +* Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672) +* Multiple JSON property fixes (#661 #662 #695) +* Fixed extension typing issue (#673 #674) +* Fixed model override behavior (#694) +* Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696) + +1.0.0 (2020-04-26) +^^^^^^^^^^^^^^^^^^ + +* Switched to Poetry for package and dependency management. +* [Breaking] Moved built-in extension modules to separate PyPI packages. +* Switched to src layout. +* Switched to black code style. +* Better documentation. +* [Breaking] ``none_as_none()`` is now always enabled. +* Added representation method for engine. +* Protected the URL instance fed to ``set_bind()`` from manipulation. +* Replaced some ``assert`` with ``AssertionError`` (#258 #655) + + +GINO 0.8 +-------- + +This is also version 1.0 release candidate. + +Migrating to GINO 0.8 +^^^^^^^^^^^^^^^^^^^^^ + +1. contextvars +"""""""""""""" + +We introduced aiocontextvars_ 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the ``enable_inherit()`` or +``disable_inherit()`` calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created **after** importing ``gino`` or ``aiocontextvars``, or the patch +won't work correctly. + +There is nothing to worry about in Python 3.7. + +2. none_as_none +""""""""""""""" + +When GINO tries to load a row with all ``NULL`` values into an instance, it +will now by default return ``None`` instead of an instance with all ``None`` +attributes. To recover the default behavior of 0.7, please specify +``none_as_none(False)`` in affected model loader. + +This is especially applicable to relationship sub-loaders - if the sub-loader +found it all ``NULL``, no instance will be set to parent instance. For +example:: + + child = await Child.load(parent=Parent).query.gino.first() + +If ``child.parent_id`` is ``NULL`` in database, then the ``child`` instance +won't be called with any ``setattr(child, 'parent', ...)`` at all. (If you need +``child.parent == None`` in this case, consider setting default value +``parent = None`` in child model.) + +Please note, it is deprecated to disable ``none_as_none``, and disabling will +be removed in GINO 1.0. + +0.8.7 (2020-04-19) +^^^^^^^^^^^^^^^^^^ + +* Improved error handling when attribute names collide (Contributed by Reskov in #637 #638) +* Fixed ``with_bind`` usability in aiohttp extension (#518) + +0.8.6 (2020-02-10) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``JSONPathType`` bind processor for asyncpg (#609) +* Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600) +* Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629) +* Added ``distinct()`` to ``Alias`` (#628) + +0.8.5 (2019-11-19) +^^^^^^^^^^^^^^^^^^ + +* Improved support for ``__tablename__`` in ``declared_attr`` (Contributed by Roald Storm in #592) + +0.8.4 (2019-11-09) +^^^^^^^^^^^^^^^^^^ + +* Better loader support for models in subqueries (#573 #585) +* Allowed ``__tablename__`` to be a ``declared_attr`` (#579 #582) +* Fixed Sanic 19.9.0 compatibility (#569) +* Added one() and one_or_none() (Contributed by Ilaï Deutel in #577) +* Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538) +* Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533) +* Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520) +* Fixed Grammar (Contributed by Simeon J Morgan in #504) + +0.8.3 (2019-06-06) +^^^^^^^^^^^^^^^^^^ + +* Fixed deprecated warnings in asyncpg dialect and aiohttp (#425) +* Customizable db attribute name in aiohttp app instance (#457) +* Added Starlette support (#486) + +0.8.2 (2019-03-07) +^^^^^^^^^^^^^^^^^^ + +* Added exception for unknown JSON properties (#406 #408) +* Supported Quart 0.7 (#411) +* Accepted kwargs for db init in extensions (#407 #427) +* Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440) +* Added NullPool (#437 #441) +* Unpinned dependency versions (#447) +* Added support for SQLAlchemy 1.3 (#433 #451) + +0.8.1 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Alias supported ``Label`` (#365) +* Docs update (#308, 4c59ad, #401 by Pascal van Kooten) +* Version requirement for SQLAlchemy is updated to ``>=1.2`` (#378 #382) +* Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) + * And all other extensions (#395) +* Supported Tornado 5 (#396, also thanks to Vladimir Goncharov) +* Fixed custom JSON/JSONB type support (#402 #403) + +(Most fixes done by Tony Wang) + +0.8.0 (2018-10-16) +^^^^^^^^^^^^^^^^^^ + +* Welcome Tony Wang to the maintenance team (#335) +* Allowed custom column names (#261 #297) +* Allowed column instance in ``model.load()`` (Contributed by Jekel in #323) +* [Breaking] Upgraded to aiocontextvars 0.2.0 (#333) +* Fixed bug that the same empty stack is shared between sub-tasks (#313 #334) +* [Breaking] Made ``none_as_none()`` the default behavior (#351) +* Bug fixes and docs update + + +GINO 0.7 +-------- + +This is also version 1.0 beta 3. + +0.7.7 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Backported fix for custom JSON/JSONB type support (#402 #403) + +0.7.6 (2018-08-26) +^^^^^^^^^^^^^^^^^^ + +* Updated library support (Contributed by Tony Wang in #275 #309) +* Added ``none_as_none()`` (#281 #282) +* Added ``ARRAY`` alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289) +* Added ``Model.lookup()`` to prevent updating whole table without primary key (#287 #288) +* Added ``DB_ECHO`` in extension options (Contributed by Mykyta Holubakha in #298) +* Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305) +* Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304) +* Fixed to raise ``UninitializedError`` if bind is ``None`` (Contributed by Tony Wang in #307 #310) + +0.7.5 (2018-07-26) +^^^^^^^^^^^^^^^^^^ + +* Added friendly error message when using abstract models by mistake (#224) +* Supported Python 3.7 (Contributed by Tony Wang in #265) +* Updated documentation +* Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280) + +0.7.4 (2018-06-10) +^^^^^^^^^^^^^^^^^^ + +* Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228) +* Added Quart support (#213) +* Fixed Tornado options parsing (#231) +* Improved coding style and test coverage + +0.7.3 (2018-05-19) +^^^^^^^^^^^^^^^^^^ + +* Fix for failing binary type (#225) + +0.7.2 (2018-05-15) +^^^^^^^^^^^^^^^^^^ + +* Added prepared statement support (#14) +* Added dsn in extension config (Contributed by Yurii Shtrikker in #215) + +0.7.1 (2018-05-03) +^^^^^^^^^^^^^^^^^^ + +* Added support for inline model constraints (Contributed by Kinware in #198) +* Added docs and tests for using SSL (#202) +* Added ``declared_attr`` (#204) +* Allowed ``ModelLoader`` passively load partial model (#216) + +0.7.0 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Added Python 3.5 support (#187) +* Added support to use ``dict`` as ident for ``Model.get`` (#192) +* Added result loader (partial relationship support) (#13) +* Added documentation on relationship and transaction (#146) + + +GINO 0.6 +-------- + +This is also version 1.0 beta 2. + +Migrating to GINO 0.6 +^^^^^^^^^^^^^^^^^^^^^ + +1. Task Local +""""""""""""" + +We created a new Python package aiocontextvars_ from previous ``local.py``. If +you made use of the task local features, you should install this package. + +Previous ``gino.enable_task_local()`` and ``gino.disable_task_local()`` are +replaced by ``aiocontextvars.enable_inherit()`` and +``aiocontextvars.disable_inherit()``. However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars_ by default offers task +local even without ``enable_inherit()``, which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars_ is installed. + +There is no ``gino.get_local()`` and ``gino.reset_local()`` relevant in +aiocontextvars_. The similar thing is ``aiocontextvars.ContextVar`` instance +through its ``get()``, ``set()`` and ``delete()`` methods. + +Previous ``gino.is_local_root()`` is now +``not aiocontextvars.Context.current().inherited``. + +2. Engine +""""""""" + +GINO 0.6 hides ``asyncpg.Pool`` behind the new SQLAlchemy-alike +``gino.GinoEngine``. Instead of doing this in 0.5:: + + async with db.create_pool('postgresql://...') as pool: + # your code here + +You should change it to this in 0.6:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +This equals to:: + + engine = await gino.create_engine('postgresql://...') + db.bind = engine + try: + # your code here + finally: + db.bind = None + await engine.close() + +Or:: + + engine = await db.set_bind('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Or even this:: + + db = await gino.Gino('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Choose whichever suits you the best. + +Obviously ``GinoEngine`` doesn't provide ``asyncpg.Pool`` methods directly any +longer, but you can get the underlying ``asyncpg.Pool`` object through +``engine.raw_pool`` property. + +``GinoPool.get_current_connection()`` is now changed to ``current_connection`` +property on ``GinoEngine`` instances to support multiple engines. + +``GinoPool.execution_option`` is gone, instead ``update_execution_options()`` +on ``GinoEngine`` instance is available. + +``GinoPool().metadata`` is gone, ``dialect`` is still available. + +``GinoPool.release()`` is removed in ``GinoEngine`` and ``Gino``, the +``release()`` method on ``GinoConnection`` object should be used instead. + +These methods exist both in 0.5 ``GinoPool`` and 0.6 ``GinoEngine``: +``close()``, ``acquire()``, ``all()``, ``first()``, ``scalar()``, ``status()``. + +3. GinoConnection +""""""""""""""""" + +Similarly, ``GinoConnection`` in 0.6 is no longer a subclass of +``asyncpg.Connection``, instead it has a ``asyncpg.Connection`` instance, +accessable through ``GinoConnection.raw_connection`` property. + +``GinoConnection.metadata`` is deleted in 0.6, while ``dialect`` remained. + +``GinoConnection.execution_options()`` is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior. + +``GinoConnection.release()`` is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +``permanent=False`` to remain its previous behavior. + +And ``all()``, ``first()``, ``scalar()``, ``status()``, ``iterate()``, +``transaction()`` remained in 0.6. + +4. Query API +"""""""""""" + +All five query APIs ``all()``, ``first()``, ``scalar()``, ``status()``, +``iterate()`` now accept the same parameters as SQLAlchemy ``execute()``, +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter ``bind`` if they did. Just use the API on the ``GinoEngine`` or +``GinoConnection`` object instead. + +5. Transaction +"""""""""""""" + +Transaction interface is rewritten. Now in 0.6, a ``GinoTransaction`` object is +provided consistently from all 3 methods:: + + async with db.transaction() as tx: + # within transaction + + async with engine.transaction() as tx: + # within transaction + + async with engine.acquire() as conn: + async with conn.transaction() as tx: + # within transaction + +And different usage with ``await``:: + + tx = await db.transaction() + try: + # within transaction + await tx.commit() + except: + await tx.rollback() + raise + +The ``GinoConnection`` object is available at ``tx.connection``, while +underlying transaction object from database driver is available at +``tx.transaction`` - for asyncpg it is an ``asyncpg.transaction.Transaction`` +object. + +0.6.6 (2018-05-18) +^^^^^^^^^^^^^^^^^^ + +* Backported a fix for failing binary type (#225) + +0.6.5 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Abandoned 0.6.4 and keep 0.6.x stable +* Backported doc for transaction + +0.6.4 (2018-04-16) +^^^^^^^^^^^^^^^^^^ + +Abandoned version, please use 0.7.0 instead. + +0.6.3 (2018-04-08) +^^^^^^^^^^^^^^^^^^ + +* Added aiohttp support +* Added support for calling ``create()`` on model instances (Contributed by Kinware in #178 #180) +* Fixed ``get()`` by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184) + +0.6.2 (2018-03-24) +^^^^^^^^^^^^^^^^^^ + +* Fixed SQLAlchemy prefetch issue (#141) +* Fixed issue that mixin class on Model not working (#174) +* Added more documentation (Thanks Olaf Conradi for reviewing) + +0.6.1 (2018-03-18) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``create`` and ``drop`` for ``Enum`` type (#160) +* A bit more documentation (#159) + +0.6.0 (2018-03-14) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] API Refactored, ``Pool`` replaced with ``Engine`` + + * New API ``Engine`` replaced asyncpg ``Pool`` (#59) + * Supported different dialects, theoretically + * Used aiocontextvars_ instead of builtin task local (#89) +* [Breaking] Fixed query API with ``multiparams`` (executemany) to return correctly (#20) +* [Breaking] The query methods no longer accept the parameter ``bind`` +* [Breaking] ``Gino`` no longer exposes ``postgresql`` types +* Added ``echo`` on engine (#142) +* Added tests to cover 80% of code +* Added ``gino`` extension on ``SchemaItem`` for ``create_all`` and so on (#76 #106) +* Added ``gino`` extension on model classes for ``create()`` or ``drop()`` +* Added ``_update_request_cls`` on ``CRUDModel`` (#147) +* Rewrote the documentation (#146) + +.. _aiocontextvars: https://github.com/fantix/aiocontextvars + + +GINO 0.5 +-------- + +This is also version 1.0 beta 1. + +0.5.8 (2018-02-14) +^^^^^^^^^^^^^^^^^^ + +* Preparing for 0.6.0 which will be a breaking release +* Fixed wrong value of ``Enum`` in creation (Contributed by Sergey Kovalev in #126) + +0.5.7 (2017-11-24) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.6. + +* Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114) +* Added ``Model.outerjoin`` + +0.5.6 (2017-11-23) +^^^^^^^^^^^^^^^^^^ + +* Changed to use unnamed statement when possible (#80 #90) +* Added more example (Contributed by Kentoseth in #109) +* Added ``Model.join`` and made ``Model`` selectable (Contributed by Ádám Barancsuk in #112 #113) + +0.5.5 (2017-10-18) +^^^^^^^^^^^^^^^^^^ + +* Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87) +* Added ability to reset local storage (#84) +* Fixed bug in JSON property update +* Added update chaining feature + +0.5.4 (2017-10-04) +^^^^^^^^^^^^^^^^^^ + +* Updated example (Contributed by Kinware in #75) +* Added ``Model.insert`` (Contributed by Neal Wang in #63) +* Fixed issue that non-lazy acquiring fails dirty (#79) + +0.5.3 (2017-09-23) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``no module named cutils`` error (Contributed by Vladimir Goncharov in #73) + +0.5.2 (2017-09-10) +^^^^^^^^^^^^^^^^^^ + +* Added missing driver name on dialect (#67) +* Fixed dialect to support native decimal type (#67) + +0.5.1 (2017-09-09) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.0. + +* Reverted the extension, back to pure Python (#60) +* Used SQLAlchemy ``RowProxy`` +* Added ``first_or_404`` +* Fixed bug that ``GinoPool`` cannot be inherited + +0.5.0 (2017-09-03) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten + + * Extracted CRUD operations + * Core operations are moved to ``dialect`` and execution context + * Removed ``guess_model``, switched to explicit execution options + * Turned ``timeout`` parameter to an execution option + * Extracted ``pool``, ``connection`` and ``api`` from ``asyncpg_delegate`` +* Added support for SQLAlchemy execution options, and a few custom options +* [Breaking] Made `Model.select` return rows by default (#39) +* Moved `get_or_404` to extensions (#38) +* Added iterator on model classes (#43) +* Added Tornado extension (Contributed by Vladimir Goncharov) +* Added `Model.to_dict` (#47) +* Added an extension module to update `asyncpg.Record` with processed results + + +Early Development Releases +-------------------------- + +Considered as alpha releases. + + +0.4.1 (2017-08-20) +^^^^^^^^^^^^^^^^^^ + +* Support ``select`` on model instance + +0.4.0 (2017-08-15) +^^^^^^^^^^^^^^^^^^ + +* Made ``get_or_404`` more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31) +* Delegated ``sqlalchemy.__all__`` (Contributed by Neal Wang in #10 #33) +* [Breaking] Rewrote JSON/JSONB support (#29) +* Added ``lazy`` parameter on ``db.acquire`` (Contributed by Binghan Li in #32) +* Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34) +* Fixed ``iterate`` API to be compatible with asyncpg (#32) +* Unified exceptions +* [Breaking] Changed ``update`` API (#29) +* Bug fixes + +0.3.0 (2017-08-07) +^^^^^^^^^^^^^^^^^^ + +* Supported ``__table_args__`` (#12) +* Introduced task local to manage connection in context (#19) +* Added ``query.gino`` extension for in-place execution +* Refreshed README (#3) +* Adopted PEP 487 (Contributed by Tony Wang in #17 #27) +* Used ``weakref`` on ``__model__`` of table and query (Contributed by Tony Wang) +* Delegated asyncpg ``timeout`` parameter (Contributed by Neal Wang in #16 #22) + +0.2.3 (2017-08-04) +^^^^^^^^^^^^^^^^^^ + +* Supported any primary key (Contributed by Tony Wang in #11) + +0.2.2 (2017-08-02) +^^^^^^^^^^^^^^^^^^ + +* Supported SQLAlchemy result processor +* Added rich support on JSON/JSONB +* Bug fixes + +0.2.1 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Added ``update`` and ``delete`` API + +0.2.0 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Changed API, no longer reuses asyncpg API + +0.1.1 (2017-07-25) +^^^^^^^^^^^^^^^^^^ + +* Added ``db.bind`` +* API changed: parameter ``conn`` renamed to optional ``bind`` +* Delegated asyncpg Pool with ``db.create_pool`` +* Internal enhancement and bug fixes + +0.1.0 (2017-07-21) +^^^^^^^^^^^^^^^^^^ + +* First release on PyPI. diff --git a/docs/en/1.1b2/_sources/tutorials.rst.txt b/docs/en/1.1b2/_sources/tutorials.rst.txt new file mode 100644 index 0000000..6b3bb98 --- /dev/null +++ b/docs/en/1.1b2/_sources/tutorials.rst.txt @@ -0,0 +1,9 @@ +Tutorials +========= + +.. toctree:: + :glob: + + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* diff --git a/docs/en/1.1b2/_sources/tutorials/announcement.rst.txt b/docs/en/1.1b2/_sources/tutorials/announcement.rst.txt new file mode 100644 index 0000000..f159e02 --- /dev/null +++ b/docs/en/1.1b2/_sources/tutorials/announcement.rst.txt @@ -0,0 +1,297 @@ +官宣:Python 异步编程再添一利器 +=============================== + + GINO 填补了国内外 asyncio ORM 领域的空白 + +随着 Tornado\ [1]_ 和 asyncio\ [2]_ 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。\ +在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +`GINO `__ 了解一下! + +.. image:: ../images/python-gino.webp + :align: center + + +GINO 是谁 +--------- + +GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义\ [3]_\ 手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,\ +你肯定要问一句“这是谁”。 + +ORM,即关系对象映射(Object-Relational Mapping\ [4]_\ ),是一类开发人员喜闻乐见的效率工具,\ +它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢? + +因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性\ +(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),\ +创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:\ +性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 ``current_user.name`` +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试? + +传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,\ +但应用到大型项目中却十分考验开发人员的平均水平。 + +所有这些问题如果再放进异步编程的环境里,那就是 O(n\ :sup:`2`) 的复杂度了——哦不,是 +O(2\ :sup:`n`)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。 + +所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,\ +我索性在 1.0 稳定版发布的前夕做个年终总结。 + + +先说“方便快捷” +-------------- + + “方便快捷”主要说的是开发效率。 + +重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,\ +开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。 + +:: + + from gino import Gino + db = Gino() + class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + +这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟? + +没有错,这就是 SQLAlchemy ORM\ [5]_ 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core\ [6]_\ (SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道\ +(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic\ [7]_\ 、各种 SQLAlchemy 的增强插件\ [8]_\ 、专业领域的 PostGIS/geoalchemy\ [9]_\ +等,GINO 全都兼容。 + +是不是十分方便、十分快捷?不止这样。 + +GINO 一站式地解决了常用 CRUD 快捷方式\ [10]_\ 、上下文管理(aiocontextvars\ [11]_\ [12]_\ )、 +数据库事务封装和嵌套\ [13]_\ 、连接池管理和懒加载\ [14]_\ 等多项便捷功能,无额外依赖关系,即装即用。 + +:: + + daisy = await User.create(name="daisy") + await daisy.update(name="Daisy").apply() + +GINO 还提供了\ [15]_\ 各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado\ [1]_\ 、\ +aiohttp\ [16]_\ 、Sanic\ [17]_\ 、FastAPI\ [18]_\ /Starlette\ [19]_\ 、Quart\ [20]_\ +什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。 + +为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa\ [21]_ 的降维打击: + +1. 最少侵入型\ [22]_\ :SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。 +2. 终身不婚型\ [23]_\ :天生厌恶“对象”,只愿定义“表”,空手接 SQL。 +3. 火力全开型\ [24]_\ :最大程度的便利,非典型异步 ORM。 + +最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、\ +一秒可读百万行的 asyncpg\ [25]_\ ,以及 uvloop\ [26]_\ (可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,\ +深受俄罗斯和乌克兰人民的爱戴。 + + +再说“简单明了” +-------------- + + Explicit is better than implicit. + + Simple is better than complex. + + –The Zen of Python, PEP 20\ [27]_ + +Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,\ +因此 GINO 的很多设计都受到了明确性的影响。 + +比如说,GINO 的 ``Model`` 是完全无状态的普通 Python 对象(POPO\ [28]_\ —— 例如前面的 ``User`` +类,它的实例 ``daisy`` 就是内存里面的常规对象,你可以用 ``daisy.name`` 访问属性,也可以用 +``daisy.name = "DAISY"`` 来修改属性,或者用 ``u = User()`` 来创建新的实例,\ +这些操作都不会访问数据库,绝对绿色环保无毒副作用。 + +等到需要操作数据库的时候,你一定会有感知的。比如执行 ``INSERT`` 要用 +``u = await User.create()``\ ,而 ``UPDATE`` 则是 +``await u.update(name="Daisy").apply()``\ 。 + +.. hint:: + + 其中,``u.update(name="Daisy")`` 与 ``u.name = "Daisy"`` 类似,\ + 都是只在内存里修改对象的属性,不同的是 ``u.update()`` 还会返回一个包含本次变更的中间结果,\ + 对其执行 ``await xxx.apply()`` 则会将这些变更应用到数据库里。 + +这里的 ``await`` 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 ``await`` +就没有数据库操作。 + +另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders\ [29]_\ 。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 ``u = await User.get(1)`` 可以直接获取到 ID 为 ``1`` 的用户对象。\ +但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。\ +加载器的用法也是很简单的,比如一个用户可能写了很多本书: + +:: + + class Book(db.Model): + __tablename__ = "books" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + author_id = db.ForeignKey("users.id") + +然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者: + +:: + + query = Book.outerjoin(User).select() + loader = Book.load(author=User) + async for book in query.gino.load(loader).iterate(): + print(book.title, "written by", book.author.name) + +很简单的一个外连接查询 ``Book.outerjoin(User)``\ ,配合一个直观的加载器 +``Book.load(author=User)``\ ,就实现了: + +1. 执行 ``SELECT * FROM books LEFT JOIN users ON ...``\ ; +2. 将数据库返回结果的每一行中,属于 ``books`` 的字段加载成一个 ``Book`` 实例; +3. 然后将该行中剩下的属于 ``users`` 的字段加载成一个 ``User`` 实例; +4. 最后将 ``User`` 实例设置到 ``Book`` 实例的 ``author`` 属性上。 + +既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,\ +精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。 + + +优势与不足 +---------- + +随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM\ [30]_\ 、ORM\ [31]_\ (是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。 + +GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候\ +不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、\ +快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,\ +可以谨慎地用于生产环境了。 + +以下是近来统计到的关于 GINO 的应用案例: + +- 笔者工作上开发的一个服务:\ https://github.com/uc-cdis/metadata-service +- 还是笔者自己写的一个工具:\ https://github.com/fantix/aintq +- 一个汇率 API 服务:\ https://exchangeratesapi.io/ + + .. image:: ../images/exchangeratesapi.webp + :align: center + +- 各种 Telegram、Discord 的 Bot。 +- ArchLinux 用户包:\ https://aur.archlinux.org/packages/python-gino/ + + .. image:: ../images/archlinux.webp + :align: center + +- https://github.com/bryanforbes/gino-stubs/ +- 俄语教程:\ https://www.youtube.com/watch?v=WW4rOnfhiQY +- 高性能模板项目:\ https://github.com/leosussan/fastapi-gino-arq-uvicorn +- 还有几个商用的,但没征得同意就不贴出来了。 + +另外,GINO 还贴心地提供了中文文档\ [32]_\ ,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!): + +.. image:: ../images/docs.webp + :align: center + +GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能\ +(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。 + + +建设社会主义 +------------ + +GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 **4888 元**\ 的 PyCharm +专业版全家桶 License 一枚\ [33]_\ (没有发票)。 + +目前急需帮助的有: + +1. 各个 Web 框架插件的维护工作需要多人认领; +2. 更多的例子和文档,以及中文、俄文的翻译; +3. MySQL 的支持。 + +以及下面这些一直需要的帮助: + +1. 用 GINO,找 bug,提建议; +2. 修 bug,做功能,提 PR; +3. 维护社区,回答问题,参与讨论; +4. 最后也是最重要的:\ `去 GitHub 上给 GINO 加一颗星星! + `__ + +.. image:: ../images/happy-hacking.png + :align: center + + +关于作者 +-------- + +87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。\ +小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,\ +期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、\ +新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。\ +业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P + +特别感谢 `Tony Wang `__ 对 GINO 项目的贡献,以及\ `所有人 +`__\ 的贡献。 + + +参考文献 +-------- + +.. [1] Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado. +.. [2] Python Software Foundation. asyncio — 异步 I/O. + https://docs.python.org/zh-cn/3/library/asyncio.html. +.. [3] 维基百科. GNU. https://zh.wikipedia.org/wiki/GNU. +.. [4] 维基百科. 对象关系映射. + `https://zh.wikipedia.org/wiki/对象关系映射 + `__. +.. [5] 维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy. +.. [6] Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. + https://docs.sqlalchemy.org/en/13/core/index.html. +.. [7] Michael Bayer. Welcome to Alembic’s documentation!. + https://alembic.sqlalchemy.org/en/latest/. +.. [8] Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. + https://github.com/dahlia/awesome-sqlalchemy. +.. [9] Ricardo GarciaSilva. Does it have postgis support?. + https://github.com/python-gino/gino/issues/627. +.. [10] Fantix King. GINO 基础教程 - 增删改查. + https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations. +.. [11] Python Software Foundation. contextvars —Context Variables. + https://docs.python.org/3/library/contextvars.html. +.. [12] Fantix King. gino/aiocontextvars.py. + https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py. +.. [13] Fantix King. 数据库事务. + https://python-gino.org/docs/zh/master/how-to/transaction.html. +.. [14] Fantix King. 引擎与连接. + https://python-gino.org/docs/zh/master/explanation/engine.html. +.. [15] GINO Community. GINO Community. https://github.com/python-gino/. +.. [16] aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/. +.. [17] Sanic Community. SanicFramework. https://sanicframework.org/. +.. [18] Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/. +.. [19] Encode OSS. Starlette. https://www.starlette.io/. +.. [20] Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/. +.. [21] Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. + https://github.com/CanopyTax/asyncpgsa. +.. [22] Fantix King. 表结构定义 -GINO 引擎. + https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine. +.. [23] Fantix King. 表结构定义 - GINO core.  + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core. +.. [24] Fantix King. 表结构定义 - GINO ORM. + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm. +.. [25] MagicStack Inc. + asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. + https://github.com/MagicStack/asyncpg. +.. [26] MagicStack Inc. uvloop -Ultra fast asyncio event loop. + https://github.com/MagicStack/uvloop. +.. [27] Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/. +.. [28] Wikipedia. Plain old Java object. + https://en.wikipedia.org/wiki/Plain_old_Java_object. +.. [29] Fantix King. 加载器与关系. + https://python-gino.org/docs/zh/master/how-to/loaders.html. +.. [30] Andrey Bondar. Tortoise ORM. https://tortoise.github.io/. +.. [31] Encode OSS. ORM - An async ORM. https://github.com/encode/orm. +.. [32] Fantix King. 欢迎来到 GINO 的文档!. + https://python-gino.org/docs/zh/master/index.html. +.. [33] JetBrains s.r.o. Open Source Licenses. + https://www.jetbrains.com/community/opensource/#support. diff --git a/docs/en/1.1b2/_sources/tutorials/fastapi.rst.txt b/docs/en/1.1b2/_sources/tutorials/fastapi.rst.txt new file mode 100644 index 0000000..f256254 --- /dev/null +++ b/docs/en/1.1b2/_sources/tutorials/fastapi.rst.txt @@ -0,0 +1,669 @@ +Build a FastAPI Server +====================== + +In this tutorial, we'll build a production-ready FastAPI_ server together. +The full functional example is available `here +`__. + +Our application stack will look like this: + +.. image:: ../images/gino-fastapi.svg + :align: center + + +.. _FastAPI: https://fastapi.tiangolo.com/ + + +Start a New Project +------------------- + +Instead of pip, let's use the shiny Poetry_ to manage our project. Follow the link to +`install Poetry `_, and create our new +project in an empty directory: + + +.. code-block:: console + + $ mkdir gino-fastapi-demo + $ cd gino-fastapi-demo + $ git init + $ poetry init + +Then follow the Poetry_ guide to finish the initialization - you may say "no" to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains ``gino-fastapi-demo``. + +.. _Poetry: https://python-poetry.org/ + + +Add Dependencies +---------------- + +FastAPI_ is built on top of the Starlette_ framework, so we shall use the `GINO +extension for Starlette `_. Simply run: + +.. code-block:: console + + $ poetry add gino[starlette] + +Then let's add FastAPI_, together with the lightning-fast ASGI_ server Uvicorn_, and +Gunicorn_ as a production application server: + +.. code-block:: console + + $ poetry add fastapi uvicorn gunicorn + +For database migration, we'll use Alembic_. Because it uses normal DB-API_, we need +psycopg_ here too: + +.. code-block:: console + + $ poetry add alembic psycopg2 + +At last, let's add pytest_ in the development environment for testing. We also want to +add the requests_ library to use the Starlette_ |TestClient|_: + +.. code-block:: console + + $ poetry add -D pytest requests + +.. hint:: + + With the steps above, Poetry_ will automatically create a virtualenv_ for you + behind the scene, and all the dependencies are installed there. We will assume + using this for the rest of the tutorial. But you're free to create your own + virtualenv_, and Poetry_ will honor it when it's activated. + +That's all, this is my ``pyproject.toml`` created by Poetry_, yours should look similar: + +.. code-block:: toml + + [tool.poetry] + name = "gino-fastapi-demo" + version = "0.1.0" + description = "" + authors = ["Fantix King "] + + [tool.poetry.dependencies] + python = "^3.8" + gino = {version = "^1.0", extras = ["starlette"]} + fastapi = "^0.54.1" + uvicorn = "^0.11.3" + gunicorn = "^20.0.4" + alembic = "^1.4.2" + psycopg2 = "^2.8.5" + + [tool.poetry.dev-dependencies] + pytest = "^5.4.1" + requests = "^2.23.0" + + [build-system] + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" + +.. image:: ../images/gino-fastapi-poetry.svg + :align: right + +And there's also an auto-generated ``poetry.lock`` file with the frozen versions. The +directory layout should look like the diagram on the right. Now let's add the two files +to the Git repository (we will skip showing these git operations in future steps): + +.. code-block:: console + + $ git add pyproject.toml poetry.lock + $ git commit -m 'add project dependencies' + +.. _Starlette: https://www.starlette.io/ +.. _ASGI: https://asgi.readthedocs.io/ +.. _Uvicorn: https://www.uvicorn.org/ +.. _Gunicorn: https://gunicorn.org/ +.. _Alembic: https://alembic.sqlalchemy.org/ +.. _DB-API: https://www.python.org/dev/peps/pep-0249/ +.. _psycopg: https://www.psycopg.org/ +.. _pytest: https://docs.pytest.org/ +.. _virtualenv: https://virtualenv.pypa.io/ +.. _requests: https://requests.readthedocs.io/ + + +Write a Simple Server +--------------------- + +Now let's write some Python code. + +We'll create an extra ``src`` directory to include all the Python files, as demonstrated +in the diagram below. This is known as the "`src layout +`_" providing a cleaner hierarchy. + +.. image:: ../images/gino-fastapi-src.svg + :align: right + +The root Python package of our project is named as ``gino_fastapi_demo``, under which we +will create two Python modules: + +* ``asgi`` as the ASGI entry point - we'll feed it to the ASGI server +* ``main`` to initialize our server + +Here's ``main.py``:: + + from fastapi import FastAPI + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + return app + +And we'll simply instantiate our application in ``asgi.py``:: + + from .main import get_app + + app = get_app() + +Then run ``poetry install`` to link our Python package into the ``PYTHONPATH`` in +development mode. We'll be able to start a Uvicorn development server after that: + +.. code-block:: console + + $ poetry install + Installing dependencies from lock file + + No dependencies to install or update + + - Installing gino-fastapi-demo (0.1.0) + + $ poetry run uvicorn gino_fastapi_demo.asgi:app --reload + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + INFO: Started reloader process [53010] + INFO: Started server process [53015] + INFO: Waiting for application startup. + INFO: Application startup complete. + +The ``--reload`` option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server. + +.. hint:: + + As mentioned previously, if you're in your own virtualenv, the command ``poetry run + uvicorn`` can be simplified as just ``uvicorn``. + + ``poetry run`` is a convenient shortcut to run the following command in the + virtualenv managed by Poetry. + + +Add GINO Extension +------------------ + +.. image:: ../images/gino-fastapi-config.svg + :align: right + +Now let's add GINO to our server. + +First of all, we need a way to configure the database. In this tutorial, we'll use the +`configuration system `_ from Starlette_. +Add ``src/gino_fastapi_demo/config.py`` as follows:: + + from sqlalchemy.engine.url import URL, make_url + from starlette.config import Config + from starlette.datastructures import Secret + + config = Config(".env") + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + DB_DSN = config( + "DB_DSN", + cast=make_url, + default=URL( + drivername=DB_DRIVER, + username=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + database=DB_DATABASE, + ), + ) + DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1) + DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16) + DB_ECHO = config("DB_ECHO", cast=bool, default=False) + DB_SSL = config("DB_SSL", default=None) + DB_USE_CONNECTION_FOR_REQUEST = config( + "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True + ) + DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1) + DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1) + +This config file will load from environment variable first, if not found then from a +file named ``.env`` from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this: + +.. code-block:: console + + $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload + +.. image:: ../images/gino-fastapi-env.svg + :align: right + +Or set them in the file ``.env`` (this file must not be committed into Git, remember to +add it to ``.gitignore``): + +.. code-block:: bash + + DB_HOST=localhost + DB_USER=postgres + +Now it's time to create a PostgreSQL_ database and set the connection variables +correctly here. This is usually something like ``createdb yourdbname``, but it may vary +across different platforms, so we won't cover this part in this tutorial. + +.. tip:: + + Alternatively, you could also set ``DB_DSN`` to for example + ``postgresql://user:password@localhost:5432/dbname`` to override the other individual + config values like ``DB_HOST`` defined before ``DB_DSN``. + + If defined, ``DB_DSN`` always have the higher priority over the individual ones, + regardless of where they are defined - even if ``DB_HOST`` is defined in environment + variable and ``DB_DSN`` is defined in ``.env`` file, ``DB_HOST`` is still ignored. + Default value doesn't count. + +.. image:: ../images/gino-fastapi-models.svg + :align: right + +Then, create a new Python sub-package ``gino_fastapi_demo.models`` to encapsulate +database-related code, and add the code below to +``src/gino_fastapi_demo/models/__init__.py``:: + + from gino.ext.starlette import Gino + + from .. import config + + db = Gino( + dsn=config.DB_DSN, + pool_min_size=config.DB_POOL_MIN_SIZE, + pool_max_size=config.DB_POOL_MAX_SIZE, + echo=config.DB_ECHO, + ssl=config.DB_SSL, + use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST, + retry_limit=config.DB_RETRY_LIMIT, + retry_interval=config.DB_RETRY_INTERVAL, + ) + +At last, modify ``src/gino_fastapi_demo/main.py`` to install the GINO extension: + +.. code-block:: diff + + from fastapi import FastAPI + + + +from .models import db + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + + db.init_app(app) + return app + +Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database: + +.. code-block:: console + + WARNING: Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading... + INFO: Shutting down + INFO: Waiting for application shutdown. + INFO: Application shutdown complete. + INFO: Finished server process [63562] + INFO: Started server process [63563] + INFO: Waiting for application startup. + INFO: Connecting to the database: postgresql://fantix:***@localhost + INFO: Database connection pool created: + INFO: Application startup complete. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Create Models and API +--------------------- + +.. image:: ../images/gino-fastapi-models-users.svg + :align: right + +It's time to implement the API now. Let's say we are building a user management service, +through which we could add users, list users and delete users. + +First of all, we need a database table ``users`` to store the data, mapped to a GINO +model named ``User``. We shall add the model in ``gino_fastapi_demo.models.users``:: + + from . import db + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode(), default="unnamed") + +.. image:: ../images/gino-fastapi-views.svg + :align: right + +The model definition is simple enough to explain itself. + +Then we only have to use it properly in the API implementation, for which we'll create a +new Python sub-package ``gino_fastapi_demo.views``, and a new module +``gino_fastapi_demo.views.users`` as follows:: + + from fastapi import APIRouter + from pydantic import BaseModel + + from ..models.users import User + + router = APIRouter() + + @router.get("/users/{uid}") + async def get_user(uid: int): + user = await User.get_or_404(uid) + return user.to_dict() + + class UserModel(BaseModel): + name: str + + @router.post("/users") + async def add_user(user: UserModel): + rv = await User.create(nickname=user.name) + return rv.to_dict() + + @router.delete("/users/{uid}") + async def delete_user(uid: int): + user = await User.get_or_404(uid) + await user.delete() + return dict(id=uid) + + def init_app(app): + app.include_router(router) + +The |APIRouter|_ holds our new APIs locally, and ``init_app`` is used to integrate it +into our FastAPI application. Here we want some `inversion of control`_: let's make the +APIs plugable, so that we don't have to import all possible future views manually. We +shall use the `Entry Points`_ feature to load the dependencies. Add this code below to +``gino_fastapi_demo.main``:: + + import logging + from importlib.metadata import entry_points + + logger = logging.getLogger(__name__) + + def load_modules(app=None): + for ep in entry_points()["gino_fastapi_demo.modules"]: + logger.info("Loading module: %s", ep.name) + mod = ep.load() + if app: + init_app = getattr(mod, "init_app", None) + if init_app: + init_app(app) + +.. hint:: + + If you're running Python < 3.8, you'll need this `importlib-metadata backport + `_. + +And call it in our application factory: + +.. code-block:: diff + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + db.init_app(app) + + load_modules(app) + return app + +Finally, define the entry points in ``pyproject.toml`` following the `Poetry document +for plugins `_: + +.. code-block:: toml + + [tool.poetry.plugins."gino_fastapi_demo.modules"] + "users" = "gino_fastapi_demo.views.users" + +Run ``poetry install`` again to activate the entry points - you may need to restart the +Uvicorn_ development server manually, as the reloader cannot capture the changes we made +to ``pyproject.toml``. + +Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven't created the database tables. + +.. |APIRouter| replace:: ``APIRouter`` +.. _APIRouter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control +.. _Entry Points: https://docs.python.org/3/library/importlib.metadata.html#entry-points + + +Integrate with Alembic +---------------------- + +To get started with Alembic_, run this command in the project root directory: + +.. code-block:: console + + $ poetry run alembic init migrations + +.. image:: ../images/gino-fastapi-alembic.svg + :align: right + +This will generate a new directory ``migrations`` where Alembic_ will store database +migration revisions. At the same time, an ``alembic.ini`` file is created in the project +root directory. Let's simply add all of them to Git control. + +For Alembic_ to use our data models defined with GINO (and of course the database +config), we need to modify ``migrations/env.py`` to connect with the GINO instance: + +.. code-block:: diff + + # add your model's MetaData object here + # for 'autogenerate' support + # from myapp import mymodel + # target_metadata = mymodel.Base.metadata + -target_metadata = None + +from gino_fastapi_demo.config import DB_DSN + +from gino_fastapi_demo.main import db, load_modules + + + +load_modules() + +config.set_main_option("sqlalchemy.url", str(DB_DSN)) + +target_metadata = db + +Then create our first migration revision with: + +.. code-block:: console + + $ poetry run alembic revision --autogenerate -m 'add users table' + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.autogenerate.compare] Detected added table 'users' + Generating migrations/versions/32c0feba61ea_add_users_table.py ... done + +The generated revision file should roughly look like this:: + + def upgrade(): + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("nickname", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + def downgrade(): + op.drop_table("users") + +.. hint:: + + Whenever there is a change to the database schema in the future, just modify the + GINO models and run ``alembic revision --autogenerate`` again to generate new + revisions to track the change. Remember to review the revision file - you may want + to adjust it. + +Eventually, let's apply this migration, by upgrading to the latest revision: + +.. code-block:: console + + $ poetry run alembic upgrade head + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.runtime.migration] Running upgrade -> 32c0feba61ea, add users table + +Now all the APIs should be fully operational, try with the Swagger UI. + + +Write the Tests +--------------- + +In order not to break our development database with running tests, let's create a +separate database to run tests. Apply this change to ``gino_fastapi_demo.config``: + +.. code-block:: diff + + config = Config(".env") + + +TESTING = config("TESTING", cast=bool, default=False) + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + +if TESTING: + + if DB_DATABASE: + + DB_DATABASE += "_test" + + else: + + DB_DATABASE = "gino_fastapi_demo_test" + DB_DSN = config( + +.. hint:: + + You need to run ``createdb`` to actually create the database. If you have set + ``DB_DATABASE`` in ``.env`` - e.g. ``DB_DATABASE=mydb``, the name of the testing + database should be ``mydb_test``. Or else, ``gino_fastapi_demo_test``. + +Then, let's create our pytest_ fixture in ``tests/conftest.py``:: + + import pytest + from alembic.config import main + from starlette.config import environ + from starlette.testclient import TestClient + + environ["TESTING"] = "TRUE" + + @pytest.fixture + def client(): + from gino_fastapi_demo.main import db, get_app + + main(["--raiseerr", "upgrade", "head"]) + + with TestClient(get_app()) as client: + yield client + + main(["--raiseerr", "downgrade", "base"]) + +.. image:: ../images/gino-fastapi-tests.svg + :align: right + +This fixture creates all the database tables before running the test, yield a Starlette_ +|TestClient|_, and drop all the tables with all the data after the test to maintain a +clean environment for the next test. + +Here's a sample test in ``tests/test_users.py``:: + + import uuid + + def test_crud(client): + # create + nickname = str(uuid.uuid4()) + r = client.post("/users", json=dict(name=nickname)) + r.raise_for_status() + + # retrieve + url = f"/users/{r.json()['id']}" + assert client.get(url).json()["nickname"] == nickname + + # delete + client.delete(url).raise_for_status() + assert client.get(url).status_code == 404 + +Then run the test: + +.. code-block:: console + + $ poetry run pytest + =========================== test session starts =========================== + platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 + rootdir: gino-fastapi-demo + collected 1 item + + tests/test_users.py . [100%] + + ============================ 1 passed in 1.21s ============================ + +.. |TestClient| replace:: ``TestClient`` +.. _TestClient: https://www.starlette.io/testclient/ + + +Notes for Production +-------------------- + +Given the popularity of Docker/Kubernetes, we'll build a ``Dockerfile`` for our demo: + +.. code-block:: dockerfile + + FROM python:3.8-alpine as base + + FROM base as builder + RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev + RUN pip install poetry + COPY . /src/ + WORKDIR /src + RUN python -m venv /env && . /env/bin/activate && poetry install + + FROM base + RUN apk add --no-cache postgresql-libs + COPY --from=builder /env /env + COPY --from=builder /src /src + WORKDIR /src + CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"] + +In this ``Dockerfile``, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn_ with +|UvicornWorker|_ from Uvicorn_ as the worker class for best production reliability. + +Let's review what we have in the project. + +.. image:: ../images/gino-fastapi-layout.svg + :align: center + +This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live: + +* Set ``DB_RETRY_LIMIT`` to a larger number to allow staring the application server + before the database is fully ready. +* Implement the same retry logic in ``migrations/env.py`` so that Alembic_ gets the same + functionality. +* Enable ``DB_SSL`` if needed. +* Write a ``docker-compose.yml`` for other developers to get a quick taste or even use + it for development. +* Enable CI_, install ``pytest-cov`` and use ``--cov-fail-under`` to guarantee coverage. +* Integrate static code analysis tools and security/CVE checking tools. +* Automate Alembic_ upgrade properly - e.g. after new version is deployed. +* Be aware of the common security attacks like CSRF_, XSS_, etc. +* Write load tests. + +Again, the source code of the demo is available `here +`__, +and the source of this tutorial is `here +`__. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking! + +.. |UvicornWorker| replace:: ``UvicornWorker`` +.. _UvicornWorker: https://www.uvicorn.org/deployment/#gunicorn +.. _CI: https://en.wikipedia.org/wiki/Continuous_integration +.. _CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/docs/en/1.1b2/_sources/tutorials/tutorial.rst.txt b/docs/en/1.1b2/_sources/tutorials/tutorial.rst.txt new file mode 100644 index 0000000..d9527f3 --- /dev/null +++ b/docs/en/1.1b2/_sources/tutorials/tutorial.rst.txt @@ -0,0 +1,436 @@ +GINO Basics +=========== + +This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of: + +* RDBMS, especially PostgreSQL_ +* `Asynchronous programming in Python `_ + +Knowledge of SQLAlchemy_ is not required. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Introduction +------------ + +Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API. + +You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won't make your +code run faster, if not slower. Please read :doc:`/explanation/why` for more +information. + + +Installation +------------ + +To install GINO, run this command in your terminal: + +.. code-block:: console + + $ pip install gino + +This is the preferred method to install GINO, as it will always install the +most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +Alternatively, if you are using Poetry_ to manage your project dependencies, +you may want to run: + +.. code-block:: console + + $ poetry add gino + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ +.. _Poetry: https://python-poetry.org + + +Declare Models +-------------- + +First of all, we'll need a :class:`~gino.api.Gino` object, usually under the +name of ``db`` as a global variable:: + + from gino import Gino + + db = Gino() + +``db`` acts like a reference to the database, most database interactions will +go through it. + +"Model" is a basic concept in GINO, it is a Python class inherited from +:attr:`db.Model `. Each :class:`~gino.declarative.Model` +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let's declare a model:: + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + +By declaring this ``User`` class, we are actually defining a database table +named ``users``, with two columns ``id`` and ``nickname``. Note that the fixed +:attr:`~gino.declarative.Model.__tablename__` property is required. GINO +suggests singular for model names, and plural for table names. Each +:class:`db.Column ` property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to ``db`` types `here +`__ in the SQLAlchemy +documentation. + +.. note:: + + SQLAlchemy_ is a powerful ORM library for non-asynchronous programming in + Python, on top of which GINO is built. SQLAlchemy supports many popular + RDBMS including PostgreSQL and MySQL through different dialect + implementation, so that the same Python code can be compiled into different + SQL depending on the dialect you choose. GINO inherited this support too, + but for now there is only one dialect for PostgreSQL through asyncpg_. + +.. _asyncpg: https://github.com/MagicStack/asyncpg +.. _SQLAlchemy: https://www.sqlalchemy.org/ + +If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:: + + class Booking(db.Model): + __tablename__ = 'bookings' + + day = db.Column(db.Date) + booker = db.Column(db.String) + room = db.Column(db.String) + + _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey') + _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True) + _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room') + +It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +`here `__ in the +SQLAlchemy documentation. + +Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the :attr:`~gino.declarative.Model.__table_args__` attribute. In order +to e.g. define constraints in mixin classes, +:func:`~gino.declarative.declared_attr` is required. Please feel free to read +more about it in its API documentation. + + +Get Connected +------------- + +The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let's create a +PostgreSQL database for this tutorial: + +.. code-block:: console + + $ createdb gino + +Then we tell our ``db`` object to connect to this database:: + + import asyncio + + async def main(): + await db.set_bind('postgresql://localhost/gino') + + asyncio.get_event_loop().run_until_complete(main()) + +If this runs successfully, then you are connected to the newly created database. +Here ``postgresql`` indicates the database dialect to use (the default driver +is ``asyncpg``, you can explicitly specify that with ``postgresql+asyncpg://``, +or simply ``asyncpg://``), ``localhost`` is where the server is, and ``gino`` +is the name of the database. Check `here +`__ for more +information about how to compose this database URL. + +.. note:: + + Under the hood :meth:`~gino.api.Gino.set_bind` calls + :func:`~gino.create_engine` and bind the engine to this ``db`` object. GINO + engine is similar to SQLAlchemy engine, but not identical. Because GINO + engine is asynchronous, while the other is not. Please refer to the API + reference of GINO for more information. + +Now that we are connected, let's create the table in database (in the same +``main()`` method):: + + await db.gino.create_all() + +.. warning:: + + It is :meth:`db.gino.create_all `, + not :meth:`db.create_all `, because + ``db`` is inherited from SQLAlchemy :class:`~sqlalchemy.schema.MetaData`, + and :meth:`db.create_all ` is from + SQLAlchemy using non-asynchronous methods, which doesn't work with the + bound GINO engine. + + In practice :meth:`~gino.schema.GinoSchemaVisitor.create_all` is usually + not an ideal solution. To manage database schema, tool like Alembic_ is + recommended, please see how to :doc:`/how-to/alembic`. + +If you want to explicitly disconnect from the database, you can do this:: + + await db.pop_bind().close() + +Let's review the code we have so far together in one piece before moving on:: + + import asyncio + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + + async def main(): + await db.set_bind('postgresql://localhost/gino') + await db.gino.create_all() + + # further code goes here + + await db.pop_bind().close() + + + asyncio.get_event_loop().run_until_complete(main()) + +.. _Alembic: https://bitbucket.org/zzzeek/alembic + + +CRUD Operations +--------------- + +In order to operate on the database, one of GINO's core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations. + + +Create +^^^^^^ + +Let's start by creating a ``User``:: + + user = await User.create(nickname='fantix') + # This will cause GINO to execute this SQL with parameter 'fantix': + # INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname + +As mentioned previously, ``user`` object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:: + + print(f'ID: {user.id}') # 1 + print(f'Nickname: {user.nickname}') # fantix + +It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:: + + user = User(nickname='fantix') + user.nickname += ' (founder)' + await user.create() + +Retrieve +^^^^^^^^ + +To retrieve a model object from database by primary key, you can use the class +method :meth:`~gino.crud.CRUDModel.get` on the model class. Now let's retrieve +the same row:: + + user = await User.get(1) + # SQL (parameter: 1): + # SELECT users.id, users.nickname FROM users WHERE users.id = $1 + +Normal SQL queries are done through a class property +:attr:`~gino.crud.CRUDModel.query`. For example, let's retrieve all ``User`` +objects from database as a list:: + + all_users = await db.all(User.query) + # SQL: + # SELECT users.id, users.nickname FROM users + +Alternatively, you can use the ``gino`` extension on +:attr:`~gino.crud.CRUDModel.query`. This has exactly the same effect as above:: + + all_users = await User.query.gino.all() + # SQL: + # SELECT users.id, users.nickname FROM users + +.. note:: + + ``User.query`` is actually a SQLAlchemy query, with its own + non-asynchronous execution methods. GINO added this ``gino`` extension on + all executable SQLAlchemy clause objects to conveniently execute them in + the asynchronous way, so that it is even not needed to import the ``db`` + reference for execution. + +Now let's add some filters. For example, find all users with ID lower than 10:: + + founding_users = await User.query.where(User.id < 10).gino.all() + # SQL (parameter: 10): + # SELECT users.id, users.nickname FROM users WHERE users.id < $1 + +Read more `here `__ +about writing queries, because the query object is exactly from SQLAlchemy core. + +.. warning:: + + Once you get a model object, it is purely in memory and fully detached from + the database. That means, if the row is externally updated, the object + values remain unchanged. Likewise, changes made to the object won't affect + the database values. + + Also, GINO keeps no track of model objects, therefore getting the same row + twice returns two different object with identical values. Modifying one + does not magically affect the other one. + + Different than traditional ORMs, the GINO model objects are more like + objective SQL results, rather than stateful ORM objects. In order to adapt + for asynchronous programming, GINO is designed to be that simple. That's + also why GINO Is Not ORM. + +Sometimes we want to get only one object, for example getting the user by name +when logging in. There's a shortcut for this scenario:: + + user = await User.query.where(User.nickname == 'fantix').gino.first() + # SQL (parameter: 'fantix'): + # SELECT users.id, users.nickname FROM users WHERE users.nickname = $1 + +If there is no user named "fantix" in database, ``user`` will be ``None``. + +And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +:meth:`~gino.crud.CRUDModel.select` class method:: + + name = await User.select('nickname').where(User.id == 1).gino.scalar() + # SQL (parameter: 1): + # SELECT users.nickname FROM users WHERE users.id = $1 + print(name) # fantix + +Or get the count of all users:: + + population = await db.func.count(User.id).gino.scalar() + # SQL: + # SELECT count(users.id) AS count_1 FROM users + print(population) # 17 for example + + +Update +^^^^^^ + +Then let's try to make some modifications. In this example we'll mixin some +retrieve operations we just tried. :: + + # create a new user + user = await User.create(nickname='fantix') + + # get its name + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + assert name == user.nickname # they are both 'fantix' before the update + + # modification here + await user.update(nickname='daisy').apply() + # SQL (parameters: 'daisy', 1): + # UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname + print(user.nickname) # daisy + + # get its name again + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + print(name) # daisy + assert name == user.nickname # they are both 'daisy' after the update + +So :meth:`~gino.crud.CRUDModel.update` is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +:meth:`~gino.crud.UpdateRequest.apply` call makes the update happen in database. + +.. note:: + + GINO explicitly split the in-memory update and SQL update into two methods: + :meth:`~gino.crud.CRUDModel.update` and + :meth:`~gino.crud.UpdateRequest.apply`. :meth:`~gino.crud.CRUDModel.update` + will update the in-memory model object and return an + :class:`~gino.crud.UpdateRequest` object which contains all the + modifications. A following :meth:`~gino.crud.UpdateRequest.apply` on + :class:`~gino.crud.UpdateRequest` object will apply these recorded + modifications to database by executing a compiled SQL. + +.. tip:: + + :class:`~gino.crud.UpdateRequest` object has another method named + :meth:`~gino.crud.UpdateRequest.update` which works the same as the one + on model object, just that it combines the new modifications together with + the ones already recorded in current :class:`~gino.crud.UpdateRequest` + object, and it returns the same :class:`~gino.crud.UpdateRequest` object. + That means, you can chain the updates and end up with one + :meth:`~gino.crud.UpdateRequest.apply`, or make use of the + :class:`~gino.crud.UpdateRequest` object to combine several updates in a + batch. + +:meth:`~gino.crud.CRUDModel.update` on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the :meth:`~gino.crud.CRUDModel.update` on model class level, with a +bit difference:: + + await User.update.values(nickname='Founding Member ' + User.nickname).where( + User.id < 10).gino.status() + # SQL (parameter: 'Founding Member ', 10): + # UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2 + + name = await User.select('nickname').where( + User.id == 1).gino.scalar() + print(name) # Founding Member fantix + +There is no :class:`~gino.crud.UpdateRequest` here, everything is again +SQLAlchemy clause, its +`documentation `_ here for +your reference. + + +Delete +^^^^^^ + +At last. Deleting is similar to updating, but way simpler. :: + + + user = await User.create(nickname='fantix') + await user.delete() + # SQL (parameter: 1): + # DELETE FROM users WHERE users.id = $1 + print(await User.get(user.id)) # None + +.. hint:: + + Remember the model object is in memory? In the last :func:`print` + statement, even though the row is already deleted in database, the object + ``user`` still exists with its values untouched. + +Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):: + + await User.delete.where(User.id > 10).gino.status() + # SQL (parameter: 10): + # DELETE FROM users WHERE users.id > $1 + + +With basic :doc:`/how-to/crud`, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking! diff --git a/docs/en/1.1b2/_static/css/gino.css b/docs/en/1.1b2/_static/css/gino.css new file mode 100644 index 0000000..7f0f40b --- /dev/null +++ b/docs/en/1.1b2/_static/css/gino.css @@ -0,0 +1,826 @@ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: 100%; + box-sizing: border-box; + font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; +} + +body { + background-color: #F0F0F0; + font-weight: 500; +} + +a { + color: #3E6CDE; +} + +code, kbd, samp { + font-family: monospace; + background-color: #f8f8f8; + border: 1px solid #f0f0f0; + padding: 0 4px; + border-radius: 2px; +} + +.highlight .hll { + display: block; +} + +strong { + font-weight: 700; +} + +:root { + --v-primary-base: #212B73; + --v-secondary-base: #AD755C; + --v-accent-base: #EEF5FF; + --v-error: #FF5252; + --v-info: #BDC8F1; + --v-info-light: #3E6CDE; + --v-success: #4CAF50; + --v-success-light: rgba(76, 175, 80, 0.2); + --v-warning: #FFC107; + --v-warning-light: rgba(255, 193, 7, 0.2); + --v-border: #444b54; + --v-border-light: #c0cee1; +} + +nav { + background-color: rgba(33, 43, 115, 0.95); + -webkit-box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); +} + +.nav-wrapper { + padding: 4px 16px; + display: flex; + align-items: center; +} + +nav .brand-logo { + position: relative; + display: flex; + align-items: center; + left: 0; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +.breadcrumbs { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + -ms-overflow-style: none; +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs .breadcrumb, +.breadcrumbs .breadcrumb::before { + content: '>'; + font-size: 16px; + white-space: nowrap; +} + + +.btn-flat { + display: flex; + align-items: center; + font-weight: 500; + margin-left: 22px; + text-decoration: none; + color: #fff; + padding: 14px; + font-size: 14px; + -webkit-transition: .2s; + transition: .2s; +} + +.btn-flat:hover { + color: #f8d230; + text-shadow: #f8d230 0 0 5px; +} + +a.brackets:before, +span.brackets:empty, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +a.footnote-reference { + vertical-align: super; + font-size: 0.8em; +} + +.footnote dt { + float: left; +} + +div.search { + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +} + +div.search i { + position: absolute; + left: 10px; + transition: 0.3s; +} + +#search:focus + i { + color: var(--v-primary-base); +} + +#search { + flex: 0.1 1 192px; + background-color: rgba(255, 255, 255, 0.16); + border-radius: 28px; + height: 38px; + padding: 0 32px 0 42px; + margin: 0; + border: none; + transition: 0.3s; + color: rgba(255, 255, 255, 0.32); + font-size: 14px; +} + +#search:focus { + border: none; + background-color: #FFFFFF; + color: #000000; +} + +#search-results { + position: absolute; + left: 0; + right: -250px; + max-height: 61.8vh; + background-color: rgba(0, 0, 0, 0.8); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + overflow: scroll; + top: 51px; + padding: 16px; +} + +#search-results li { + float: none; + line-height: 1.5; + font-size: 14px; +} + +#search-results li a { + color: #4E7CEE; + padding: 0; +} + +#search-results li .context { + padding: 0 0 24px 24px; +} +#search-results .highlighted { + color: #f8d230; +} + +.theme-dark { + color: #FFFFFF; +} + +.spacer { + flex-grow: 1; +} + +.img { + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.sidenav { + top: 64px; + z-index: 900; + box-shadow: none; + bottom: 0; + height: auto; +} + +.col.body { + padding: 0; +} + +#main-content { + padding: 0 24px; + background-color: white; +} + +.sidenav ul li:first-of-type { + display: none; +} + +.sidenav ul ul li:first-of-type { + display: block; +} + +.sidenav .caption { + font-size: 1.2em; + color: var(--v-primary-base); + padding: 16px 32px 0 48px; + position: relative; + font-weight: 600; +} + +.sidenav .caption::before { + position: absolute; + display: block; + content: " "; + width: 20px; + height: 20px; + left: 16px; + top: 20px; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.sidenav li > a { + font-size: 14px; + line-height: 14px; + padding: 12px 24px 12px 48px; + height: auto; +} + +.sidenav li li > a { + padding-left: 70px; +} + +.sidenav .caption:nth-of-type(1)::before { + background-image: url(../images/tutorials-icon.svg); +} +.sidenav .caption:nth-of-type(2)::before { + background-image: url(../images/how-to-icon.svg); +} +.sidenav .caption:nth-of-type(3)::before { + background-image: url(../images/explanation-logo.svg); +} +.sidenav .caption:nth-of-type(4)::before { + background-image: url(../images/reference-logo.svg); +} + +.sidenav a.current { + background-color: var(--v-accent-base); +} + +.github { + margin-left: 16px; + transition: 0.3s; +} + +.github:hover { + text-shadow: #FFFFFF 0 0 5px; +} + +.github i { + font-size: 40px; +} + +.right-nav.col { + padding: 0; +} + +.table-of-contents { + padding: 16px; + top: 64px; + bottom: 0; + overflow-y: auto; +} + +.table-of-contents li { + padding: 0; +} + +.table-of-contents > ul > li > a { + display: none; +} + +.table-of-contents > ul ul > li > a.active, +.table-of-contents > ul ul > li > a:hover { + padding-left: 14px; +} + +.table-of-contents > ul ul ul > li > a.active, +.table-of-contents > ul ul ul > li > a:hover { + padding-left: 30px; +} + +.table-of-contents > ul ul ul > li > a { + padding-left: 32px; +} + +.table-of-contents > ul ul ul ul > li > a.active, +.table-of-contents > ul ul ul ul > li > a:hover { + padding-left: 46px; +} + +.table-of-contents > ul ul ul ul > li > a { + padding-left: 48px; +} + +.table-of-contents a { + height: auto; + padding-top: 2px; + padding-bottom: 2px; +} + +.table-of-contents a.active, +.table-of-contents a:hover { + color: var(--v-primary-base); + border-left: 2px solid var(--v-primary-base); + font-weight: 500; +} + +.table-of-contents #version-selector hr { + margin-top: 48px; +} + +.table-of-contents #version-selector div { + margin-top: 16px; + color: #757575; +} + +.table-of-contents #version-selector a { + color: #757575; +} + +.table-of-contents .add-your-lang, +.table-of-contents .add-your-lang a, +.table-of-contents .add-your-lang a:active, +.table-of-contents .add-your-lang a:hover { + border-left: none; + font-size: 14px; + text-decoration: underline; + text-align: right; + margin-top: 8px; + color: #aaa; + font-weight: normal; +} + +.table-of-contents #version-selector span, +.table-of-contents #version-selector a, +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + border-left: none; + padding: 8px; + margin-left: 16px; +} + +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + color: var(--v-info-light); +} + +.table-of-contents #version-selector span { + color: var(--v-info-light); +} + +.version-warning { + border: 1px solid #757575; + background-color: #ffaaaa; + padding: 8px; +} + +h1 { + font-size: 32px; + margin: 32px 0 20px 0; + font-weight: 600; +} + +h2 { + font-size: 26px; + margin: 18px 0 12px 0; + font-weight: 600; +} + +h3 { + font-size: 22px; + margin: 17px 0 11px 0; + font-weight: 600; +} + +h4 { + font-size: 18px; + margin: 14px 0 9px 0; + font-weight: 600; +} + +h5 { + font-size: 16px; + margin: 13px 0 8px 0; + font-weight: 600; +} + +h6 { + font-size: 15px; + margin: 12px 0 7px 0; + font-weight: 600; +} + +a.headerlink { + padding-left: 8px; + visibility: hidden; + position: absolute; + font-family: sans-serif; +} + +:hover > a.headerlink { + visibility: visible; +} + +.section { + /*margin-top: -64px;*/ + /*padding-top: 64px;*/ +} + +.highlight { + background-color: rgba(0, 0, 0, 0.8); + clear: both; +} + +.highlight pre { + border: 1px solid var(--v-border-light); + padding: 1em; + font-size: 14px; + overflow-x: auto; +} + +.body li p { + margin: 0; +} + +.body ul li { + list-style-type: disc; + margin-left: 2rem; +} + +.body ul li li { + list-style-type: circle; +} + +.body ul li li li { + list-style-type: square; +} + +.admonition { + border-radius: 4px; + padding: 1px 1rem 40px; + box-shadow: #D1CECE 0 0 4px; + background-repeat: no-repeat; + background-position: 100% calc(100% + 20px); + background-size: 128px; +} + +.admonition-title { + font-size: 18px; + font-weight: 600; + color: #3E6CDE; +} + +.admonition-title:before { + content: " "; + display: inline-block; + width: 20px; + height: 20px; + margin: 0 8px -4px 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.admonition.note, +.admonition.seealso { + background-color: #F5FAFD; + background-image: url(../images/hmm.png); +} + +.admonition.note .admonition-title:before, +.admonition.seealso .admonition-title:before { + background-image: url(../images/icon-note.svg); +} + +.admonition.tip, +.admonition.hint { + background-color: #F5FDF6; + background-image: url(../images/aha.png); +} + +.admonition.tip { + background-image: url(../images/OK.png); +} + +.admonition.hint .admonition-title:before { + background-image: url(../images/icon-hint.svg); +} + +.admonition.warning { + background-color: #FDF5F5; + background-image: url(../images/fighting.png); +} + +.admonition.warning .admonition-title:before { + background-image: url(../images/icon-warning.svg); +} + +.admonition.tip .admonition-title:before { + background-image: url(../images/icon-info.svg); +} + +.body .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 700px; +} + +.body .section ul.boxed-nav li { + border-radius: 4px; + box-shadow: #D1CECE 0 0 4px; + padding: 10px; + margin: 25px 0 0; + width: 340px; + list-style: none; + color: #202A73; + font-weight: 500; + overflow: hidden; +} + +.body .section ul.boxed-nav li div { + background-color: #F4F7FC; + border-radius: 4px; + height: 110px; + padding: 30px 30px 0; + background-image: url(../images/box-bg-dec.svg); + background-repeat: no-repeat; + background-position: -15px -10px; +} + +.body .section ul.boxed-nav li:nth-of-type(3n) div { + background-image: url(../images/box-bg-dec-2.svg); +} + +.body .section ul.boxed-nav li:nth-of-type(3n+1) div { + background-image: url(../images/box-bg-dec-3.svg); +} + +.body .section ul.boxed-nav li a img { + width: 42px; + margin: 0 30px 30px 0; + float: left; +} + +.body .section ul.boxed-nav li p { + font-size: 18px; + color: #212B73; + line-height: 18px; + margin-bottom: 8px; +} + +.body .section ul.boxed-nav li p:nth-of-type(2) { + font-size: 14px; + word-break: break-word; +} + +.body .section ul.boxed-nav p a, +.body .section ul.boxed-nav p a:visited { + color: #212B73; +} + +p.divio { + position: absolute; + font-size: 0.8em; + color: #aaa; + margin-top: 40px; +} + +p.divio a, p.divio a:visited { + color: #aaa; +} + +.github-stars { + display: flex; + align-items: center; + padding: 0; + margin-left: 32px; + border-radius: 27px; + font-size: 14px; + font-weight: 500; +} + +.github-stars div { + border: 1px solid #fff; + line-height: 27px; + background-color: #1E2B78; + transition: 0.1s; + height: 29px; +} + +.github-stars div.left { + border-bottom-left-radius: 27px; + border-top-left-radius: 27px; + border-right: none; + padding: 0 7px 0 34px; + position: relative; +} + +.github-stars div.left .github-logo { + position: absolute; + height: 29px; + top: -1px; + left: -1px; +} + +.github-stars div.right { + border-bottom-right-radius: 27px; + border-top-right-radius: 27px; + padding: 0 7px 0 7px; + display: flex; + align-items: center; +} + +.github-stars div.right img { + height: 14px; + margin-right: 7px; +} + +.github-stars:hover { + color: white; + text-shadow: none; + box-shadow: #ffffff 0 0 5px; +} + +.btn-large.white img { + width: 32px; + height: 32px; + margin-top: 6px; +} + +.btn-large.white div { + color: #3E6CDE; + position: absolute; + bottom: 0; + width: 56px; + line-height: 18px; + font-size: 14px; + font-weight: 600; +} + +.sidenav-overlay { + z-index: 897; +} + +nav a.sidenav-trigger { + height: 24px; + margin: 16px 16px 16px 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +nav a.sidenav-trigger hr { + width: 24px; + margin: 0; + border: 0; + background-color: #fff; + height: 2px; +} + +.fixed-action-btn { + display: none; +} + +@media (min-width: 993px) { + main { + padding-left: 300px; + } + + #main-content { + padding: 0 48px; + margin: 16px; + border-radius: 4px; + } + + .admonition { + margin: 1.5rem 2rem; + padding: 1px 3rem 40px; + } + + img.align-center { + max-width: 100%; + display: block; + margin: 0 auto; + } + + img.align-left { + max-width: 100%; + float: left; + margin: 0 2rem 1.5rem 0; + } + + img.align-right { + max-width: 100%; + float: right; + margin: 0 0 1.5rem 2rem; + } + + .table-of-contents { + width: calc(25vw - 75px); + } + + p.divio { + margin-left: -47px; + } + + .btn-flat { + margin-left: 22px; + padding: 14px; + } + + .sidenav { + top: 64px; + } + + nav a.sidenav-trigger { + display: none; + } +} + +@media (max-width: 992px) { + img.align-left, + img.align-center, + img.align-right { + display: block; + margin: 1.5rem auto; + max-width: 100%; + } + + .admonition { + overflow: scroll; + } + + #main-content { + padding-bottom: 56px; + } + + #main-content p { + overflow-x: scroll; + } + + main > .row { + margin-bottom: 0; + } + + .breadcrumbs { + display: none; + } + + .btn-flat { + margin-left: 10px; + padding: 5px; + } +} + +@media (max-width: 740px) { + #search-container { + display: none; + } +} + +@media (max-width: 600px) { + .sidenav { + top: 56px; + } + + .fixed-action-btn { + display: block; + } +} + +@media (max-width: 510px) { + .github-stars { + display: none; + } +} diff --git a/docs/en/1.1b2/_static/css/materialize.min.css b/docs/en/1.1b2/_static/css/materialize.min.css new file mode 100644 index 0000000..74b1741 --- /dev/null +++ b/docs/en/1.1b2/_static/css/materialize.min.css @@ -0,0 +1,13 @@ +/*! + * Materialize v1.0.0 (http://materializecss.com) + * Copyright 2014-2017 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/docs/en/1.1b2/_static/favicon.ico b/docs/en/1.1b2/_static/favicon.ico new file mode 100644 index 0000000..07b38bc Binary files /dev/null and b/docs/en/1.1b2/_static/favicon.ico differ diff --git a/docs/en/1.1b2/_static/images/OK.png b/docs/en/1.1b2/_static/images/OK.png new file mode 100644 index 0000000..81f5433 Binary files /dev/null and b/docs/en/1.1b2/_static/images/OK.png differ diff --git a/docs/en/1.1b2/_static/images/aha.png b/docs/en/1.1b2/_static/images/aha.png new file mode 100644 index 0000000..a421799 Binary files /dev/null and b/docs/en/1.1b2/_static/images/aha.png differ diff --git a/docs/en/1.1b2/_static/images/box-bg-dec-2.svg b/docs/en/1.1b2/_static/images/box-bg-dec-2.svg new file mode 100644 index 0000000..0c1e246 --- /dev/null +++ b/docs/en/1.1b2/_static/images/box-bg-dec-2.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/en/1.1b2/_static/images/box-bg-dec-3.svg b/docs/en/1.1b2/_static/images/box-bg-dec-3.svg new file mode 100644 index 0000000..0d757ca --- /dev/null +++ b/docs/en/1.1b2/_static/images/box-bg-dec-3.svg @@ -0,0 +1,25 @@ + + + + Path 10 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/1.1b2/_static/images/box-bg-dec.svg b/docs/en/1.1b2/_static/images/box-bg-dec.svg new file mode 100644 index 0000000..a6e38d5 --- /dev/null +++ b/docs/en/1.1b2/_static/images/box-bg-dec.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/en/1.1b2/_static/images/explanation-logo.svg b/docs/en/1.1b2/_static/images/explanation-logo.svg new file mode 100644 index 0000000..98df7c3 --- /dev/null +++ b/docs/en/1.1b2/_static/images/explanation-logo.svg @@ -0,0 +1,20 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/fighting.png b/docs/en/1.1b2/_static/images/fighting.png new file mode 100644 index 0000000..a7f6ddd Binary files /dev/null and b/docs/en/1.1b2/_static/images/fighting.png differ diff --git a/docs/en/1.1b2/_static/images/hmm.png b/docs/en/1.1b2/_static/images/hmm.png new file mode 100644 index 0000000..8b60eb6 Binary files /dev/null and b/docs/en/1.1b2/_static/images/hmm.png differ diff --git a/docs/en/1.1b2/_static/images/how-to-icon.svg b/docs/en/1.1b2/_static/images/how-to-icon.svg new file mode 100644 index 0000000..6506741 --- /dev/null +++ b/docs/en/1.1b2/_static/images/how-to-icon.svg @@ -0,0 +1,24 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/icon-hint.svg b/docs/en/1.1b2/_static/images/icon-hint.svg new file mode 100644 index 0000000..db86c44 --- /dev/null +++ b/docs/en/1.1b2/_static/images/icon-hint.svg @@ -0,0 +1,27 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/icon-info.svg b/docs/en/1.1b2/_static/images/icon-info.svg new file mode 100644 index 0000000..c4690a0 --- /dev/null +++ b/docs/en/1.1b2/_static/images/icon-info.svg @@ -0,0 +1,27 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/icon-note.svg b/docs/en/1.1b2/_static/images/icon-note.svg new file mode 100644 index 0000000..03308b0 --- /dev/null +++ b/docs/en/1.1b2/_static/images/icon-note.svg @@ -0,0 +1,21 @@ + + + + 编组 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/icon-warning.svg b/docs/en/1.1b2/_static/images/icon-warning.svg new file mode 100644 index 0000000..4e3ea02 --- /dev/null +++ b/docs/en/1.1b2/_static/images/icon-warning.svg @@ -0,0 +1,29 @@ + + + + 编组 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/language.svg b/docs/en/1.1b2/_static/images/language.svg new file mode 100644 index 0000000..b61c7d4 --- /dev/null +++ b/docs/en/1.1b2/_static/images/language.svg @@ -0,0 +1,16 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/reference-logo.svg b/docs/en/1.1b2/_static/images/reference-logo.svg new file mode 100644 index 0000000..b79d156 --- /dev/null +++ b/docs/en/1.1b2/_static/images/reference-logo.svg @@ -0,0 +1,22 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/images/tutorials-icon.svg b/docs/en/1.1b2/_static/images/tutorials-icon.svg new file mode 100644 index 0000000..ff76008 --- /dev/null +++ b/docs/en/1.1b2/_static/images/tutorials-icon.svg @@ -0,0 +1,21 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/js/documentation_options.js b/docs/en/1.1b2/_static/js/documentation_options.js new file mode 100644 index 0000000..092c7e1 --- /dev/null +++ b/docs/en/1.1b2/_static/js/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.1.0b2', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/en/1.1b2/_static/js/gino.js b/docs/en/1.1b2/_static/js/gino.js new file mode 100644 index 0000000..1690ada --- /dev/null +++ b/docs/en/1.1b2/_static/js/gino.js @@ -0,0 +1,642 @@ +$u = _.noConflict(); + +function splitQuery(query) { + return query.split(/\s+/); +} + +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2 +}; + +var Search = { + _index : null, + + setIndex: function(index) { + this._index = index; + }, + + performObjectSearch : function(object, otherterms) { + var filenames = this._index.filenames; + var docnames = this._index.docnames; + var objects = this._index.objects; + var objnames = this._index.objnames; + var titles = this._index.titles; + + var i; + var results = []; + + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + var fullnameLower = fullname.toLowerCase() + if (fullnameLower.indexOf(object) > -1) { + var score = 0; + var parts = fullnameLower.split('.'); + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower == object || parts[parts.length - 1] == object) { + score += Scorer.objNameMatch; + // matches in last name + } else if (parts[parts.length - 1].indexOf(object) > -1) { + score += Scorer.objPartialMatch; + } + var match = objects[prefix][name]; + var objname = objnames[match[1]][2]; + var title = titles[match[0]]; + // If more than one term searched for, we require other words to be + // found in the name/title/description + if (otherterms.length > 0) { + var haystack = (prefix + ' ' + name + ' ' + + objname + ' ' + title).toLowerCase(); + var allfound = true; + for (i = 0; i < otherterms.length; i++) { + if (haystack.indexOf(otherterms[i]) == -1) { + allfound = false; + break; + } + } + if (!allfound) { + continue; + } + } + var descr = objname + (', in ') + title; + + var anchor = match[3]; + if (anchor === '') + anchor = fullname; + else if (anchor == '-') + anchor = objnames[match[1]][1] + '-' + fullname; + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) { + score += Scorer.objPrio[match[2]]; + } else { + score += Scorer.objPrioDefault; + } + results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]); + } + } + } + + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch : function(searchterms, excluded, terms, titleterms) { + var docnames = this._index.docnames; + var filenames = this._index.filenames; + var titles = this._index.titles; + + var i, j, file; + var fileMap = {}; + var scoreMap = {}; + var results = []; + + // perform the search on the required terms + for (i = 0; i < searchterms.length; i++) { + var word = searchterms[i]; + var files = []; + var _o = [ + {files: terms[word], score: Scorer.term}, + {files: titleterms[word], score: Scorer.title} + ]; + // add support for partial matches + if (word.length > 2) { + for (var w in terms) { + if (w.match(word) && !terms[word]) { + _o.push({files: terms[w], score: Scorer.partialTerm}) + } + } + for (var w in titleterms) { + if (w.match(word) && !titleterms[word]) { + _o.push({files: titleterms[w], score: Scorer.partialTitle}) + } + } + } + + // no match but word was a required one + if ($u.every(_o, function(o){return o.files === undefined;})) { + break; + } + // found search word in contents + $u.each(_o, function(o) { + var _files = o.files; + if (_files === undefined) + return + + if (_files.length === undefined) + _files = [_files]; + files = files.concat(_files); + + // set score for the word in each file to Scorer.term + for (j = 0; j < _files.length; j++) { + file = _files[j]; + if (!(file in scoreMap)) + scoreMap[file] = {}; + scoreMap[file][word] = o.score; + } + }); + + // create the mapping + for (j = 0; j < files.length; j++) { + file = files[j]; + if (file in fileMap && fileMap[file].indexOf(word) === -1) + fileMap[file].push(word); + else + fileMap[file] = [word]; + } + } + + // now check if the files don't contain excluded terms + for (file in fileMap) { + var valid = true; + + // check if all requirements are matched + var filteredTermCount = // as search terms with length < 3 are discarded: ignore + searchterms.filter(function(term){return term.length > 2}).length + if ( + fileMap[file].length != searchterms.length && + fileMap[file].length != filteredTermCount + ) continue; + + // ensure that none of the excluded terms is in the search result + for (i = 0; i < excluded.length; i++) { + if (terms[excluded[i]] == file || + titleterms[excluded[i]] == file || + $u.contains(terms[excluded[i]] || [], file) || + $u.contains(titleterms[excluded[i]] || [], file)) { + valid = false; + break; + } + } + + // if we have still a valid result we can add it to the result list + if (valid) { + // select one (max) score for the file. + // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... + var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); + results.push([docnames[file], titles[file], '', null, score, filenames[file]]); + } + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurrence, the + * latter for highlighting it. + */ + makeSearchSummary : function(htmlText, keywords, hlwords) { + var text = Search.htmlToText(htmlText); + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
    ').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; + }, + + htmlToText : function(htmlString) { + var htmlElement = document.createElement('span'); + htmlElement.innerHTML = htmlString; + $(htmlElement).find('.headerlink').remove(); + docContent = $(htmlElement).find('[role=main]')[0]; + if(docContent === undefined) { + console.warn("Content block not found. Sphinx search tries to obtain it " + + "via '[role=main]'. Could you check your theme or template."); + return ""; + } + return docContent.textContent || docContent.innerText; + }, + + query: function(query) { + this.out = $('#search-results'); + this.out.empty(); + this.output = $(''),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0'),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); \ No newline at end of file diff --git a/docs/en/1.1b2/_static/js/underscore.js b/docs/en/1.1b2/_static/js/underscore.js new file mode 100644 index 0000000..5b55f32 --- /dev/null +++ b/docs/en/1.1b2/_static/js/underscore.js @@ -0,0 +1,31 @@ +// Underscore.js 1.3.1 +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, +h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= +b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== +null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= +function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= +e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= +function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, +c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; +b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, +1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; +b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; +b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), +function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ +u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= +function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= +true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/docs/en/1.1b2/_static/logo.svg b/docs/en/1.1b2/_static/logo.svg new file mode 100644 index 0000000..78bb2b4 --- /dev/null +++ b/docs/en/1.1b2/_static/logo.svg @@ -0,0 +1,42 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/_static/pygments.css b/docs/en/1.1b2/_static/pygments.css new file mode 100644 index 0000000..5b510e8 --- /dev/null +++ b/docs/en/1.1b2/_static/pygments.css @@ -0,0 +1,77 @@ +pre { line-height: 125%; margin: 0; } +td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } +span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } +td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #49483e } +.highlight { background: #272822; color: #f8f8f2 } +.highlight .c { color: #75715e } /* Comment */ +.highlight .err { color: #960050; background-color: #1e0010 } /* Error */ +.highlight .k { color: #66d9ef } /* Keyword */ +.highlight .l { color: #ae81ff } /* Literal */ +.highlight .n { color: #f8f8f2 } /* Name */ +.highlight .o { color: #f92672 } /* Operator */ +.highlight .p { color: #f8f8f2 } /* Punctuation */ +.highlight .ch { color: #75715e } /* Comment.Hashbang */ +.highlight .cm { color: #75715e } /* Comment.Multiline */ +.highlight .cp { color: #75715e } /* Comment.Preproc */ +.highlight .cpf { color: #75715e } /* Comment.PreprocFile */ +.highlight .c1 { color: #75715e } /* Comment.Single */ +.highlight .cs { color: #75715e } /* Comment.Special */ +.highlight .gd { color: #f92672 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gi { color: #a6e22e } /* Generic.Inserted */ +.highlight .go { color: #66d9ef } /* Generic.Output */ +.highlight .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #75715e } /* Generic.Subheading */ +.highlight .kc { color: #66d9ef } /* Keyword.Constant */ +.highlight .kd { color: #66d9ef } /* Keyword.Declaration */ +.highlight .kn { color: #f92672 } /* Keyword.Namespace */ +.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ +.highlight .kr { color: #66d9ef } /* Keyword.Reserved */ +.highlight .kt { color: #66d9ef } /* Keyword.Type */ +.highlight .ld { color: #e6db74 } /* Literal.Date */ +.highlight .m { color: #ae81ff } /* Literal.Number */ +.highlight .s { color: #e6db74 } /* Literal.String */ +.highlight .na { color: #a6e22e } /* Name.Attribute */ +.highlight .nb { color: #f8f8f2 } /* Name.Builtin */ +.highlight .nc { color: #a6e22e } /* Name.Class */ +.highlight .no { color: #66d9ef } /* Name.Constant */ +.highlight .nd { color: #a6e22e } /* Name.Decorator */ +.highlight .ni { color: #f8f8f2 } /* Name.Entity */ +.highlight .ne { color: #a6e22e } /* Name.Exception */ +.highlight .nf { color: #a6e22e } /* Name.Function */ +.highlight .nl { color: #f8f8f2 } /* Name.Label */ +.highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +.highlight .nx { color: #a6e22e } /* Name.Other */ +.highlight .py { color: #f8f8f2 } /* Name.Property */ +.highlight .nt { color: #f92672 } /* Name.Tag */ +.highlight .nv { color: #f8f8f2 } /* Name.Variable */ +.highlight .ow { color: #f92672 } /* Operator.Word */ +.highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ +.highlight .mf { color: #ae81ff } /* Literal.Number.Float */ +.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ +.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ +.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ +.highlight .sa { color: #e6db74 } /* Literal.String.Affix */ +.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ +.highlight .sc { color: #e6db74 } /* Literal.String.Char */ +.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ +.highlight .sd { color: #e6db74 } /* Literal.String.Doc */ +.highlight .s2 { color: #e6db74 } /* Literal.String.Double */ +.highlight .se { color: #ae81ff } /* Literal.String.Escape */ +.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ +.highlight .si { color: #e6db74 } /* Literal.String.Interpol */ +.highlight .sx { color: #e6db74 } /* Literal.String.Other */ +.highlight .sr { color: #e6db74 } /* Literal.String.Regex */ +.highlight .s1 { color: #e6db74 } /* Literal.String.Single */ +.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ +.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #a6e22e } /* Name.Function.Magic */ +.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ +.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ +.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ +.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/en/1.1b2/explanation.html b/docs/en/1.1b2/explanation.html new file mode 100644 index 0000000..4dd7afb --- /dev/null +++ b/docs/en/1.1b2/explanation.html @@ -0,0 +1,240 @@ + + + + + + + + Explanation - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/explanation/async.html b/docs/en/1.1b2/explanation/async.html new file mode 100644 index 0000000..c5b1e45 --- /dev/null +++ b/docs/en/1.1b2/explanation/async.html @@ -0,0 +1,375 @@ + + + + + + + + Asynchronous Programming 101 - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Asynchronous Programming 101

    +
    +

    The Story

    +

    Let’s say we want to build a search engine. We’ll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this:

    +../_images/why_single_task.png +

    We have lots of web pages to index, so we simply handle them one by one:

    +../_images/why_throughput.png +

    Let’s assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores:

    +../_images/why_multicore.png +

    This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading:

    +../_images/why_multithreading.png +

    Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What’s wrong with multi-threading? From the diagram we can see:

    +
      +
    • There are yellow bars taking up extra time.

    • +
    • The green bars can still overlap with any bar in the other thread, but

    • +
    • non-green bars cannot overlap with non-green bars in the other thread.

    • +
    +

    The yellow bars are time taken by context switches, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let’s assume a world without +Hyper-threading or similar), +so in order to run several threads concurrently the CPU must split its +time into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point.

    +

    Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it’s waiting for the HTTP response (I/O). That’s how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won’t be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That’s also why this is called concurrency instead of parallelism.

    +

    As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous C10k +problem, usually solved by +asynchronous I/O:

    +../_images/why_coroutine.png +
    +

    Note

    +

    Asynchronous I/O and coroutines are two different things, but they usually +go together. Here we will stick with coroutines for simplicity.

    +
    +

    Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem.

    +
    +
    +

    Cooperative multitasking

    +

    So what is a coroutine?

    +

    In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread.

    +

    Threads are scheduled by the operating system using an approach called preemptive +multitasking. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn’t +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this:

    +
    Thread 1: I wanna run!
    +OS: Okay, here you go...
    +Thread 2: I wanna run!
    +OS: Urh, alright one sec ... Thread 1, hold on for a while!
    +Thread 1: Well I'm not done yet, but you are the boss.
    +OS: It won't be long. Thread 2 it's your turn now.
    +Thread 2: Yay! (&%#$@..+*&#)
    +Thread 1: Can I run now?
    +OS: Just a moment please ... Thread 2, give it a break!
    +Thread 2: Alright ... but I really need the CPU.
    +OS: You'll have it later. Thread 1, hurry up!
    +
    +
    +

    In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don’t - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +cooperative multitasking. It’s like this:

    +
    Coroutine 1: Let me know when event A arrives. I'm done here before that.
    +Event manager: Okay. What about you, coroutine 2?
    +Coroutine 2: Um I've got nothing to do here before event B.
    +Event manager: Cool, I'll be watching.
    +Event manager: (after a while) Hey coroutine 1, event A is here!
    +Coroutine 1: Awesome! Let me see ... looks good, but I need event C now.
    +Event manager: Very well. Seems event B arrived just now, coroutine 2?
    +Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done.
    +Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while.
    +(silence)
    +Event manager: Damn, event C timed out!
    +Coroutine 1: Arrrrh gotta kill myself with an exception :S
    +Event manager: Up to you :/
    +
    +
    +

    For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That’s why in the last diagram, +the red bars are not interleaved like threads.

    +
    +

    Tip

    +

    In Python and asyncio, async def declares coroutines, await yields +control to event loop (event manager).

    +
    +
    +
    +

    Pros and cons

    +

    Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput.

    +

    With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code.

    +

    However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple recv() operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to recv(), repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O.

    +

    Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, sleep(1) +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between await finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind.

    +

    Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It’s not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/explanation/engine.html b/docs/en/1.1b2/explanation/engine.html new file mode 100644 index 0000000..07fd9ca --- /dev/null +++ b/docs/en/1.1b2/explanation/engine.html @@ -0,0 +1,682 @@ + + + + + + + + Engine and Connection - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Engine and Connection

    +

    GinoEngine is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together:

    +../_images/engine.png +

    Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users.

    +

    During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use.

    +
    +

    Note

    +

    In SQLAlchemy, database drivers are supposed to follow the DB-API standard, +which does not usually provide a pool implementation. Therefore, SQLAlchemy +has its own pool implementation, created directly in engine. This is where +this diagram doesn’t fit SQLAlchemy.

    +
    +

    The pool creates raw connections, not the GinoConnection +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we’ll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries.

    +

    On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use.

    +
    +

    Note

    +

    Another difference to SQLAlchemy here: GINO execution methods always return +final results, while in SQLAlchemy accessing the result may cause further +implicit database accesses. Therefore GINO engine immediately releases the +connection when the execution method on the engine returns, but SQLAlchemy +can only release the connection implicitly when the result data is found +exhausted.

    +

    By immediately releasing a connection, GINO may not release the related raw +connection when the raw connection was reused from another parent +connection. We’ll get to this later.

    +
    +

    GINO also supports implicit execution +without having to specify an engine or connection explicitly. This is done by +binding the engine to the db instance, also known as the +MetaData or the Gino instance. +You may possibly bind a GinoConnection instance, but that +is greatly not recommended because it is very much untested.

    +

    At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +db instance on creation, therefore the models can do implicit execution too +if their db has a bind.

    +

    Then let’s get to some details.

    +
    +

    Creating Engines

    +

    GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO’s strategy to create asynchronous GinoEngine is +just gino, but only available after gino is imported:

    +
    import gino, sqlalchemy
    +
    +async def main():
    +    e = await sqlalchemy.create_engine('postgresql://...', strategy='gino')
    +    # e is a GinoEngine
    +
    +
    +
    +

    Tip

    +

    Please read this SQLAlchemy document +to learn about writing database URLs.

    +
    +

    Also the GINO strategy replaces the default driver of dialect postgresql:// +from psycopg2 to asyncpg, so that you don’t have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +postgresql+asyncpg://... or just asyncpg://....

    +

    GINO also offers a shortcut as gino.create_engine(), which only sets the +default strategy to gino and does nothing more. So here is an identical +example:

    +
    import gino
    +
    +async def main():
    +    e = await gino.create_engine('postgresql://...')
    +    # e is also a GinoEngine
    +
    +
    +

    As you may have noticed, when using the GINO strategy, +create_engine() returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default.

    +

    For it is just SQLAlchemy create_engine(), the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!):

    +

    For Dialect:

    + +

    For Engine:

    + +

    While these parameters are discarded by GINO:

    + +

    In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from create_pool(). +For example, we can create an engine without initial connections:

    +
    e = await gino.create_engine('postgresql://...', min_size=0)
    +
    +
    +

    Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:

    +
    import sqlalchemy
    +
    +metadata = sqlalchemy.MetaData()
    +metadata.bind = 'postgresql://...'
    +
    +# or in short
    +
    +metadata = sqlalchemy.MetaData('postgresql://...')
    +
    +
    +

    This implicitly calls create_engine() under the hood. However +in GINO, creating an engine requires await, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass Gino, reverted it to simple assignment:

    +
    import gino
    +
    +db = gino.Gino()
    +
    +async def main():
    +    # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind
    +    engine = await gino.create_engine('postgresql://...')
    +    db.bind = engine
    +
    +
    +

    And provided a shortcut to do so:

    +
    engine = await db.set_bind('postgresql://...')
    +
    +
    +

    And another simpler shortcut for one-time usage:

    +
    db = await gino.Gino('postgresql://...')
    +
    +
    +

    To unset a bind and close the engine:

    +
    engine, db.bind = db.bind, None
    +await engine.close()
    +
    +
    +

    Or with a shortcut correspondingly:

    +
    await engine.pop_bind().close()
    +
    +
    +

    Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +
    +
    +

    Managing Connections

    +

    With a GinoEngine at hand, you can acquire connections +from the pool now:

    +
    conn = await engine.acquire()
    +
    +
    +

    Don’t forget to release it after use:

    +
    await conn.release()
    +
    +
    +

    Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    Here conn is a GinoConnection instance. As mentioned +previously, GinoConnection is mapped to an underlying raw +connection, as shown in following diagram:

    +../_images/connection.png +

    Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: reuse and +lazy. They are keyword arguments on acquire() +and by default switched off.

    +
    +

    reuse

    +

    When acquiring a GinoConnection (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +GinoConnection (2). This is the default behavior of +acquire() with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:

    +
    async with engine.acquire() as conn1:
    +    async with engine.acquire() as conn2:
    +        # conn2 is a completely different connection than conn1
    +
    +
    +

    But sometimes conn2 may exist in a different method:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner()
    +
    +async def inner():
    +    async with engine.acquire() as conn2:
    +        # ...
    +
    +
    +

    And we probably wish inner could reuse the same raw connection in +outer to save some resource, or borrow a new one if inner is +individually called without outer:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner(conn2=None):
    +    if conn2 is None:
    +        async with engine.acquire() as conn2:
    +            # ...
    +    else:
    +        # the same ... again
    +
    +
    +

    This is exactly the scenario reuse could be useful. We can simply tell the +acquire() to reuse the most recent reusable +connection in current context by setting reuse=True, as presented in this +identical example:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner():
    +    async with engine.acquire(reuse=True) as conn2:
    +        # ...
    +
    +
    +

    Back to previous diagram, the blue GinoConnection +instances (3, 4, 6) are “reusing connections” acquired with reuse=True, +while the green ones (2, 5, 7) are not, thus they become “reusable +connections”. The green reusable connections are put in a stack in current +context, so that acquire(reuse=True) always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack.

    +
    +

    Tip

    +

    By context, we are actually referring to the context concept in +contextvars the +new module in Python 3.7, and its partial backport aiocontextvars. Simply speaking, you may +treat a series of function calls in a chain as in the same context, even if +there is an await. It’s something like a thread local in asyncio.

    +
    +

    GinoConnection (2) may be created through +acquire(reuse=True) too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection.

    +

    Releasing a reusing connection won’t cause the reused raw connection being +returned to the pool, only directly releasing the reused +GinoConnection can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more.

    +
    +
    +

    lazy

    +

    As you may have found, GinoConnection (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set lazy=True on acquire.

    +

    A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, GinoConnection (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +
    +
    +

    On implementation level, lazy is extremely easy in +acquire(): if lazy=False then borrow a raw +connection, else do nothing. That’s it. Before executing a query or starting a +transaction, GinoConnection will always try to borrow a +raw connection if there is none present. This allows GINO to “transiently +release” a raw connection, while all GinoConnection +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don’t want to +waste a connection resource checked out for nothing. For example:

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +    await conn.release(permanent=False)        # release (8)
    +    await asyncio.sleep(10)                    # simulate long I/O work
    +    await conn.scalar('select now()')          # re-acquire a new raw connection,
    +                                               #   not necessarily the same (8)
    +
    +
    +

    When used together with reuse, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set lazy=False on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It’s been quite a while, let me post the same diagram again:

    +../_images/connection.png +
    +
    +

    reusable

    +

    Usually, you don’t have to worry about the two options reuse and lazy, +using the default acquire() will always create +a concrete GinoConnection with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use reusable=False on acquire. As shown in the diagram, the unreusable +GinoConnection is an orphan away from any stack:

    +
    async with engine.acquire():                    # (2)
    +    async with engine.acquire(reusable=False):  # the unreusable connection
    +        async with engine.acquire(reuse=True):  # (3)
    +
    +
    +

    Unreusable connections can be lazy. But it is usually meaningless to specify +both reuse=True and reusable=False at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn’t make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don’t use +acquire(reuse=True, reusable=False) unless you know what it does.

    +
    +
    +

    current_connection

    +

    Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +current_connection which is always the reusable +GinoConnection at the top of current stack, or None +if current stack is empty.

    +
    +

    Tip

    +

    The different between current_connection +and acquire(reuse=True) is, the +latter always produces a GinoConnection, while the +former may not.

    +
    +
    +
    +
    +

    Executing Queries

    +

    Once you have a GinoConnection instance, you can start +executing queries with it. There are 6 variants of the execute method: +all(), +first(), +one(), +one_or_none(), +scalar() and +status(). They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results:

    +
      +
    • all() returns all results in a +list, which may be empty when the query has no result, empty but +still a list.

    • +
    • first() returns the first result directly, +or None if there is no result at all. There is usually some optimization +behind the scene to efficiently get only the first result, instead of loading +the full result set into memory.

    • +
    • one() returns exactly one result. If there +is no result at all or if there are multiple results, an exception is raised.

    • +
    • one_or_none() is similar to +one(), but it returns None if there is +no result instead or raising an exception.

    • +
    • scalar() is similar to +first(), it returns the first value of the +first result. Quite convenient to just retrieve a scalar value from database, +like NOW(), MAX(), COUNT() or whatever generates a single value. +None is also returned when there is no result, it is up to you how to +distinguish no result and the first value is NULL.

    • +
    • status() executes the query and discard all +the query results at all. Instead it returns the execution status line as it +is, usually a textual string. Note, there may be no optimization to only +return the status without loading the results, so make your query generate +nothing if you don’t want any result.

    • +
    +

    By “result”, I meant RowProxy of SQLAlchemy - an +immutable row instance with both tuple and dict interfaces. +Database values are translated twice before they are eventually stored in a +RowProxy: first by the database driver (dialect) +from network payload to Python objects (see Type Conversion of +how asyncpg does this), second by SQLAlchemy +result_processor() depending on the actual +type and dialect.

    +

    The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy execute() (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +multiparams, all 4 methods will always return None discarding all +results. Likewise, the parameter values are processed twice too: first by +bind_processor() then the database driver.

    +

    GINO also supports SQLAlchemy +execution_options() provided either on +engine level, +connection level or on +queries. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling return_model and providing a model will make +all() and +first() return ORM model instance(s) instead +of RowProxy instance(s). See also +execution_options() for more information.

    +

    In addition, GINO has an iterate() method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a server-side cursor.

    +
    +
    +

    Implicit Execution

    +

    Acquire a GinoConnection and execute queries on it, that +is the most explicit way. You can also execute queries on a +GinoEngine instance. In this case, a connection will be +acquired with reuse=True for you implicitly, and released after returning:

    +
    await engine.scalar('select now()')
    +
    +
    +

    Equals to:

    +
    async with engine.acquire(reuse=True) as conn:
    +    await conn.scalar('select now()')
    +
    +
    +

    This allows you to easily write connectionless code. For example:

    +
    async def get_now():
    +    return await engine.scalar('select now()')
    +
    +async def main():
    +    async with engine.acquire():
    +        now = await get_now()
    +        await engine.status('UPDATE ...')
    +
    +
    +

    In this example, main() will take only one raw connection. get_now() +can also work alone out of any acquire() context, thanks to reuse.

    +

    Furthermore, GINO provides the same query APIs on Gino +directly. They are simply delegates to corresponding API methods on the +bind. This allows even engine-less programming:

    +
    db = gino.Gino()
    +
    +async def get_now():
    +    return await db.scalar('select now()')
    +
    +async def main():
    +    async with db.with_bind('postgresql://...'):
    +        now = await get_now()
    +        await db.status('UPDATE ...')
    +
    +
    +
    +

    Note

    +

    In this example we didn’t put the two queries in an acquire() block, so +they might be executed in two different connections.

    +
    +

    At last, the SQLAlchemy implicit execution +on queries also work in GINO, under an extension named gino:

    +
    await users_table.select().gino.all()
    +
    +
    +

    By default, the extension GinoExecutor is injected on +Executable as a property of name gino +at the creation of Gino instance. Therefore, any +Executable object has the gino +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the bind of the db instance.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/explanation/sa20.html b/docs/en/1.1b2/explanation/sa20.html new file mode 100644 index 0000000..d320a35 --- /dev/null +++ b/docs/en/1.1b2/explanation/sa20.html @@ -0,0 +1,824 @@ + + + + + + + + SQLAlchemy 2.0 - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    SQLAlchemy 2.0

    +

    This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes.

    +

    SQLAlchemy 2.0 will +deliver many breaking API changes, and SQLAlchemy 1.4 will be the “interim” +version for people to eventually upgrade their software to use SQLAlchemy 2.0.

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    GINO

    SQLAlchemy

    Dialect

    Comments

    1.0.x

    1.3.x

    Custom

    Current (old-)stable.

    1.1.x

    1.3.x

    Custom

    Next old-stable.

    1.2.x

    1.3.x

    Custom

    Future old-stable (maybe).

    1.4.x

    1.4.x

    Upstream

    2.0 Interim.

    2.0.x

    2.0.x

    Upstream

    Future stable.

    2.1.x

    2.0.x

    Upstream

    Future stable iterations.

    +

    To make things easier, GINO will (luckily) also follow the same versions for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only.

    +

    At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won’t match SQLAlchemy versions.

    +
    +

    The Async Solution

    +

    Among all the exciting updates in SQLAlchemy 1.4 / 2.0, native async support is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet to mix asynchronous stuff into current code base, avoiding making everything +async.

    +

    Let’s say we have an asynchronous method to create an asyncpg connection:

    +
    import asyncpg
    +
    +async def connect():
    +    return await asyncpg.connect("postgresql:///")
    +
    +
    +

    And an end-user method to use it:

    +
    async def main():
    +    conn = await connect()
    +    now = await conn.fetchval("SELECT now()")
    +
    +
    +

    Now instead of directly calling connect() from main(), I would like to add some +additional logic - let’s say, a sanity check:

    +
    async def safe_connect():
    +    conn = await connect()
    +    try:
    +        await conn.execute("SELECT 1")
    +    except Exception:
    +        return None
    +    else:
    +        return conn
    +
    +
    +

    Then the end-user should modify main() to:

    +
     async def main():
    +     conn = await safe_connect()
    +     if conn:
    +         now = await conn.fetchval("SELECT now()")
    +
    +
    +

    OK, everything works so far, as they are all regular async code. Here’s the interesting +part: safe_connect() must not be an async def method. With SQLAlchemy 1.4+, we +could:

    +
     from sqlalchemy.util import await_only, greenlet_spawn
    +
    + def sync_safe_connect():
    +     conn = await_only(connect())
    +     try:
    +         await_only(conn.execute("SELECT 1"))
    +     except Exception:
    +         return None
    +     else:
    +         return conn
    +
    + async def safe_connect():
    +     return await greenlet_spawn(sync_safe_connect)
    +
    +
    +

    Behind the scene, greenlet_spawn() runs the given “sync” method in a greenlet, which +uses await_only() to switch to the event loop and bridge the underlying async +methods. As sync_safe_connect() is just a normal Python method, you can imagine how +it works together with lots of other “sync” code asynchronously.

    +

    We’re not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its “sync” code base, and still being able to provide async +APIs on top of them.

    +
    +
    +

    Async SQLAlchemy

    +

    Although greenlet might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move.

    +

    The sync library existed for years, with many assumptions like using threading.Lock +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. asyncio.Lock. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues.

    +

    As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, threading.Lock.acquire() actually works fine in a single coroutine, but 2 +concurrent coroutines +acquiring the same threading.Lock may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread.

    +

    Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base.

    +

    However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that’s GINO’s part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won’t work +because there is no place for await in accessing attributes (GINO doesn’t like such +implicitness anyways, so yeah).

    +

    To quickly get a picture of async SQLAlchemy (Core), here’s a sample from SQLAlchemy:

    +
    import asyncio
    +
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def async_main():
    +    engine = create_async_engine(
    +        "postgresql+asyncpg://scott:tiger@localhost/test", echo=True,
    +    )
    +
    +    async with engine.begin() as conn:
    +        await conn.run_sync(meta.drop_all)
    +        await conn.run_sync(meta.create_all)
    +
    +        await conn.execute(
    +            t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
    +        )
    +
    +    async with engine.connect() as conn:
    +
    +        # select a Result, which will be delivered with buffered
    +        # results
    +        result = await conn.execute(select(t1).where(t1.c.name == "some name 1"))
    +
    +        print(result.fetchall())
    +
    +
    +asyncio.run(async_main())
    +
    +
    +
    +
    +

    Auto-Commit Complication

    +

    After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that BEGIN starts a transaction and +COMMIT / ROLLBACK ends it. But what is happening to SQL statements that is not +wrapped in BEGIN ... COMMIT blocks?

    +
    +

    If you do not issue a BEGIN command, then each individual statement has an +implicit BEGIN and (if successful) COMMIT wrapped around it.

    +

    —PostgreSQL Documentation, 3.4. Transactions

    +
    +

    And yes, implicit ROLLBACK if not successful. This is not directly named as an +“auto-commit” feature, but PostgreSQL does enforce it. Now imagine a database whose +“auto-commit” feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed.

    +

    PEP 249 (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only commit() and rollback() on a +connection, but no begin(). So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call commit() to persist your changes. Closing a connection will +cause pending transactions rolled back automatically.

    +
    +

    Note that if the database supports an auto-commit feature, this (the auto-commit +feature – GINO comments) must be initially off.

    +

    —PEP 249

    +
    +

    As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +“auto-commit” has to mimic such behavior. For example, psycopg2 will automatically emit +a BEGIN to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen IDLE IN TRANSACTION?), sometimes even +holding database locks and eventually causing a deadlock storm.

    +

    To work around this workaround, PEP 249 does say:

    +
    +

    An interface method may be provided to turn it (the auto-commit feature) back on.

    +
    +

    So for psycopg2, one could do this:

    +
    import psycopg2
    +
    +conn = psycopg2.connect("postgresql:///")
    +conn.autocommit = True
    +conn.cursor().execute("SELECT now()")
    +
    +
    +

    Now the database correctly receives this SELECT statement only, without any implicit +BEGIN surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:

    +
    conn.cursor().execute("BEGIN")
    +conn.cursor().execute("UPDATE ...")
    +conn.cursor().execute("COMMIT")
    +
    +
    +

    Or 2) turn auto-commit off again:

    +
    conn.autocommit = False
    +conn.cursor().execute("UPDATE ...")
    +conn.commit()
    +
    +
    +

    I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg does provide a cleaner API, by not complying to PEP 249:

    +
    import asyncpg
    +
    +async def main():
    +    conn = await asyncpg.connect("postgresql://")
    +
    +    print(await conn.fetchval("SELECT now()"))  # SELECT now();
    +
    +    async with conn.transaction():              # BEGIN;
    +        await conn.execute("UPDATE ...")        # UPDATE ...;
    +                                                # COMMIT;
    +
    +
    +

    It’s much cleaner to see what’s actually happening on the wire to the database. This is +also how GINO works.

    +
    +
    +

    SQLAlchemy for DB-API

    +

    Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:

    +
    import sqlalchemy as sa
    +
    +e = sa.create_engine("postgresql:///", future=True)
    +with e.connect() as conn:
    +    conn.scalar(sa.text("SELECT now()"))
    +
    +
    +

    Only SELECT now()? No. Here’s the answer:

    +
    with e.connect() as conn:                 # BEGIN; SELECT version(); ...; ROLLBACK;
    +    conn.scalar(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                          # ROLLBACK;
    +
    +
    +
    +

    Note

    +

    We are using SQLAlchemy 2.0 API for simplification, by setting future=True using +SQLAlchemy 1.4. It’s way more complicated in SQLAlchemy 1.3 and I don’t want to get +into that.

    +
    +

    The reason behind this is, connections have “auto-commit” turned off by default. If +there is no transaction, an implicit BEGIN will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg and +simulated a compatible DB-API. Like this:

    +
    import sqlalchemy as sa
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def main():
    +    e = create_async_engine("postgresql+asyncpg:///")
    +    async with e.connect() as conn:                  # BEGIN; SELECT version(); ...; ROLLBACK;
    +        await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                     # ROLLBACK;
    +
    +
    +

    If you want to modify the database permanently, you have to commit() the implicit +transaction explicitly:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("UPDATE ..."))    # BEGIN; UPDATE ...;
    +         await conn.commit()                          # COMMIT;
    +
    +
    +

    Or use the explicit transaction API:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Please note that, SQLAlchemy 2.0 doesn’t allow soft-nested transactions. In other words, +you cannot nest 2 async with conn.begin(): blocks like this:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():      # BEGIN;
    +             async with conn.begin():  # Error: a transaction is already begun
    +                 ...
    +
    +
    +

    This limitation applies to implicit transactions too, even though it’s weird:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         await conn.rollback()                        # ROLLBACK;
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Similar to Core, SQLAlchemy ORM follows the same principal. Grab a session, use +it without begin(), and when you want to commit, commit(). Or, use an explicit +transaction in a with session.begin(): block. Personally I don’t like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more.

    +
    +
    +

    SQLAlchemy AUTOCOMMIT

    +

    I know you already miss the WYSIWYG asyncpg and GINO API. Hang in there, let’s build +GINO 1.4 together with the SQLAlchemy AUTOCOMMIT feature.

    +

    To turn AUTOCOMMIT back on, we need to set the isolation_level to AUTOCOMMIT in +execution_options:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Hooray! No more implicit BEGIN magic. We’re one step closer.

    +
    +

    Note

    +

    There is also a keyword argument:

    +
     e = create_async_engine(
    +     "postgresql+asyncpg:///",
    +     isolation_level="AUTOCOMMIT",
    + )
    +
    +
    +

    But this is implemented very differently than execution_options, and I don’t +think it’s working for GINO’s use case.

    +
    +

    The next question is, how do we explicitly start a transaction? Let’s try begin():

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    Wait a second … we’ve seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we’re in AUTOCOMMIT mode? Even +though the isolation_level tell the driver not to send BEGIN to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.begin():                     # no-op
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +
    +
    +

    Well, not quite what we expected. With AUTOCOMMIT set, all of begin(), commit() +and rollback() become no-ops.

    +

    Similar to the answers in psycopg2, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let’s try 2):

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    It’s working! According to SQLAlchemy docs, execution_options() creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well…

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                      # ROLLBACK;
    +
    +
    +

    Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same “DB-API” connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting isolation_level +modifies the value on “DB-API” connection.

    +

    Returning a SQLAlchemy connection back to the pool resets the isolation_level to its +default value, and acquiring the same connection again will initialize the +isolation_level with values from execution_options of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +isolation_level again:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         conn.execution_options(isolation_level="AUTOCOMMIT")
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Eventually we made it! 🎉

    +

    Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:

    +
    import gino
    +
    +async def main():
    +    engine = await gino.create_engine("postgresql:///")
    +    async with engine.acquire() as conn:
    +        await conn.scalar("SELECT now()")    # SELECT now();
    +
    +        async with conn.transaction():       # BEGIN;
    +            await conn.status("UPDATE ...")  # UPDATE ...;
    +                                             # COMMIT;
    +
    +
    +
    +

    Hint

    +

    Now I feel that “implementing” auto-commit feature is more like restoring to the +original database behavior, and having auto-commit turned off by default should be +considered as a new feature called “auto-begin” or “implicit transaction”. And it’s +a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem.

    +
    +
    +
    +

    Isolation Levels

    +

    By far, we only used 2 isolation_level values:

    +
      +
    • AUTOCOMMIT

    • +
    • READ COMMITTED

    • +
    +

    AUTOCOMMIT is not a valid PostgreSQL isolation level. It’s only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the “auto-begin” simulation.

    +

    READ COMMITTED is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction:

    +
    # BEGIN;
    +BEGIN
    +
    +# SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +# ROLLBACK;
    +ROLLBACK
    +
    +
    +

    To start a transaction in a different isolation level, you may:

    +
     # BEGIN ISOLATION LEVEL SERIALIZABLE;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit BEGIN in place, an implicit transaction is used. So this SQL also works +individually:

    +
    # SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +
    +

    But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels:

    +
     # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    + SET
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # BEGIN;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    + # BEGIN ISOLATION LEVEL READ COMMITTED;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  read committed
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    Then let’s see how SQLAlchemy with asyncpg solves this problem:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "SERIALIZABLE"},
    +     )
    +     async with e.connect() as conn:
    +         async with conn.begin():  # BEGIN ISOLATION LEVEL SERIALIZABLE;
    +             await conn.execute(sa.text("UPDATE ..."))  # UPDATE ...;
    +                                                        # COMMIT;
    +
    +
    +

    Under the neath, SQLAlchemy is leveraging asyncpg’s +Connection.transaction(isolation="...") to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions.

    +

    But there are 2 issues:

    +
      +
    • User-defined isolation level is not applied in PostgreSQL implicit transactions +(a.k.a. auto-commit statements), because no one SET SESSION.

    • +
    • asyncpg has a bug that Connection.transaction(isolation="read_committed") always +emit BEGIN without explicit isolation level, regardless of the actual default +isolation level.

    • +
    +

    The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL:

    +
     import sqlalchemy as sa
    + from sqlalchemy import event
    + from sqlalchemy.dialects.postgresql.base import PGDialect
    + from sqlalchemy.ext.asyncio import create_async_engine
    +
    +
    + async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +
    +     def set_isolation_level(dbapi_conn, record):
    +         PGDialect.set_isolation_level(
    +             e.sync_engine.dialect,
    +             dbapi_conn,
    +             "SERIALIZABLE",
    +         )
    +
    +     event.listen(e.sync_engine, "connect", set_isolation_level)
    +
    +     async with e.connect() as conn:
    +         print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL")))
    +         # Outputs: serializable
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/explanation/why.html b/docs/en/1.1b2/explanation/why.html new file mode 100644 index 0000000..f54785a --- /dev/null +++ b/docs/en/1.1b2/explanation/why.html @@ -0,0 +1,410 @@ + + + + + + + + Why Asynchronous ORM? - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Why Asynchronous ORM?

    +

    Conclusion first: in many cases, you don’t need to use an asynchronous ORM, or even +asyncio itself. But when you do, it’s very important to get it done correctly.

    +
    +

    When asyncio Helps

    +

    As Mike Bayer - the author of SQLAlchemy - pointed out, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn’t make sense to say “Hey I wanna use asyncio +because I love this asynchronous ORM”.

    +

    The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +Asynchronous Programming 101. So the ultimate reason to use asyncio should be the application itself, +not the database.

    +

    For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn’t send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it’ll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead.

    +../_images/263px-Minimum-Tonne.svg.png +

    Another example is authentication using OpenID Connect Authorization Code Flow as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it’s delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +Liebig’s law, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it’s not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave.

    +

    Arbitrary delays may happen in the database too. In PostgreSQL, you can LISTEN to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case.

    +

    In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is:

    +
    +
    +

    How to Access Database

    +

    The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let’s assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don’t get me wrong - asyncpg is great and convenient to use, but +there won’t be such a question “why asynchronous ORM” if we’re not seeking an objective +layer over bare SQL. It’s totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it’s just out of our scope here.

    +

    Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won’t work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here’s a very simple server:

    +
    import asyncio
    +from fastapi import FastAPI
    +from sqlalchemy import create_engine
    +from sqlalchemy.orm import sessionmaker
    +
    +app = FastAPI()
    +e = create_engine("postgresql://localhost")
    +Session = sessionmaker(bind=e)
    +
    +
    +@app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    This follows advices found in When do I construct a Session, when do I commit it, and when do I close it? from SQLAlchemy - linking +the session scope to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is kill -9.

    +

    What just happened is called a resource starvation. While the first 10 +requests were waiting for the async work (sleep(0.1)), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default max_overflow=10) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th’s acquisition.

    +

    The root cause of such starvation is making asynchronous calls in a transaction +scope created implicitly by the default autocommit=False. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in transaction scopes by explicitly ending them:

    +
    @app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        session.rollback()  # return the connection to avoid starvation
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    Because of the implicit nature of typical ORMs, it’s very hard to identify all such +transaction scopes and make sure there’s no await in them - you may have started +an implicit transaction by simply accessing a property like current_user.name. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this.

    +

    What about thread pool?

    +

    The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let’s assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool.

    +

    This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you’d want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you’re doing that in +the opposite way, you may want to reconsider the design.

    +

    In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs.

    +
    +
    +

    Asynchronous ORM Done Right

    +

    I would describe a proper asynchronous ORM as follows:

    +
    +

    Don’t Starve.

    +

    The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything async.

    +

    In the example above, if the connection acquisition is asynchronous, it won’t block the +main thread any more (it’ll block it’s own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:

    +
    @app.get("/")
    +async def read_root():
    +    async with db.acquire() as conn:  # }
    +        async with conn.begin():      # } These two won't block the main thread
    +            now = await db.scalar("SELECT now()")
    +            await asyncio.sleep(0.1)  # do some async work
    +            return str(now)
    +
    +
    +

    Even though this code won’t cause any resource starvation, using await within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won’t kill the server immediately, but +it has a few disadvantages:

    +
      +
    1. As the database connection pool is usually much smaller than the asynchronous server +concurrency, this kind of code caps the concurrency down to the DB pool level.

    2. +
    3. Taking a database connection from the pool for nothing is a waste of resource, +especially when the database pool is the shortest stave.

    4. +
    5. Database transactions should be kept short as much as possible. Because long-hanging +transactions may keep certain database locks, leading to performance issues or even +triggering a chain reaction of deadlocks.

    6. +
    +
    +
    +

    Be explicit.

    +

    With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an await or async with, so that you’ll know for sure when a +statement is trying to make any database I/O. Fortunately there’s no other way in +asyncio to do this - following the pattern of Twisted, asyncio is already forcing +explicit await for any asynchronous operations.

    +

    Being explicit in other part of the ORM design is also useful for enhancing the quality +of users’ code. This has nothing to do with asynchronous, it’s not a golden standard or +something with a clear cut either. For example:

    +
      +
    • We could design the ORM model instances to be stateless, so that the users don’t have +to learn and worry about maintaining the state of the instances.

    • +
    • There shouldn’t be any “buffered operations” which users could “flush” with a single +statement once for all.

    • +
    • The user should just give direct one-off commands and the ORM executes them right away.

    • +
    • Also I think the convenience tooling should be well-balanced, the user doesn’t have to +guess or remember what an API means - for example, there’re more than one ways to load +a many-to-one relationship, I’d prefer to write the query by myself rather than trying +to remember what “join_without_n_plus_1()” means.

    • +
    +
    +
    +

    Be productive.

    +

    Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two?

    +

    I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn’t harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO’s design.

    +

    Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they’ve learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again.

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/genindex.html b/docs/en/1.1b2/genindex.html new file mode 100644 index 0000000..270c3b2 --- /dev/null +++ b/docs/en/1.1b2/genindex.html @@ -0,0 +1 @@ +

    genindex.html

    \ No newline at end of file diff --git a/docs/en/1.1b2/how-to.html b/docs/en/1.1b2/how-to.html new file mode 100644 index 0000000..48a26c7 --- /dev/null +++ b/docs/en/1.1b2/how-to.html @@ -0,0 +1,286 @@ + + + + + + + + How-to Guides - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    How-to Guides

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/alembic.html b/docs/en/1.1b2/how-to/alembic.html new file mode 100644 index 0000000..cbd5287 --- /dev/null +++ b/docs/en/1.1b2/how-to/alembic.html @@ -0,0 +1,297 @@ + + + + + + + + Use Alembic - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Use Alembic

    +

    Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO.

    +

    To add migrations to project first of all, add alembic as dependency:

    +
    $ pip install --user alembic
    +
    +
    +

    When you need to set up alembic for your project.

    +

    Prepare sample project. We will have a structure:

    +
    alembic_sample/
    +    my_app/
    +        models.py
    +
    +
    +

    Inside models.py define simple DB Model with GINO:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +
    +

    Set up Alembic

    +

    This will need to be done only once. Go to the main folder of your project +alembic_sample and run:

    +
    $ alembic init alembic
    +
    +
    +

    Alembic will create a bunch of files and folders in your project directory. One of them +will be alembic.ini. Open alembic.ini (you can find it in the main project +folder alembic_sample). Now change property sqlalchemy.url = with your DB +credentials. Like this:

    +
    sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    +
    +
    +

    Next go to folder alembic/ and open env.py file. Inside the env.py file you +need to import the db object. In our case db object is db from models +modules. This is a variable that links to your Gino() instance.

    +

    Inside alembic/env.py:

    +
    from main_app.models import db
    +
    +
    +

    And change target_metadata = to:

    +
    target_metadata = db
    +
    +
    +

    That’s it. We finished setting up Alembic for a project.

    +
    +

    Note

    +

    All alembic commands must be run always from the folder that contains the +alembic.ini file.

    +
    +
    +
    +

    Create first migration revision

    +

    Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema.

    +
    $ alembic revision -m "first migration" --autogenerate --head head
    +
    +
    +

    If you have any problems relative to package imports similar to this example:

    +
    File "alembic/env.py", line 7, in <module>
    +    from main_app.models import db
    +ModuleNotFoundError: No module named 'main_app'
    +
    +
    +

    Either install your project locally with pip install -e ., poetry install or +python setup.py develop, or add you package to PYTHONPATH, like this:

    +
    $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample
    +
    +
    +

    After the successful run of alembic revision in folder alembic/versions you will +see a file with new migration.

    +
    +
    +

    Apply migration on DB

    +

    Now time to apply migration to DB. It will create tables based on you DB Models.

    +
    $ alembic upgrade head
    +
    +
    +

    Great. Now you apply your first migration. Congratulations!

    +

    Next time, when you will make any changes in DB models just do:

    +
    $ alembic revision -m "your migration description" --autogenerate --head head
    +
    +
    +

    And

    +
    alembic upgrade head
    +
    +
    +

    Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/bakery.html b/docs/en/1.1b2/how-to/bakery.html new file mode 100644 index 0000000..daad040 --- /dev/null +++ b/docs/en/1.1b2/how-to/bakery.html @@ -0,0 +1,403 @@ + + + + + + + + Bake Queries - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Bake Queries

    +
    +

    New in version 1.1.

    +
    +

    Baked queries are used to boost execution performance for constantly-used queries. +Similar to the Baked Queries in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to bake them before creating the engine.

    +

    GINO provides two approaches for baked queries:

    +
      +
    1. Low-level Bakery API

    2. +
    3. High-level Gino.bake() integration

    4. +
    +
    +

    Use Bakery with Bare Engine

    +

    First, we need a bakery:

    +
    import gino
    +
    +bakery = gino.Bakery()
    +
    +
    +

    Then, let’s bake some queries:

    +
    db_time = bakery.bake("SELECT now()")
    +
    +
    +

    Or queries with parameters:

    +
    user_query = bakery.bake("SELECT * FROM users WHERE id = :uid")
    +
    +
    +

    Let’s assume we have this users table defined in SQLAlchemy Core:

    +
    import sqlalchemy as sa
    +
    +metadata = sa.MetaData()
    +user_table = sa.Table(
    +    "users", metadata,
    +    sa.Column("id", sa.Integer, primary_key=True),
    +    sa.Column("name", sa.String),
    +)
    +
    +
    +

    Now we can bake a similar query with SQLAlchemy Core:

    +
    user_query = bakery.bake(
    +    sa.select([user_table]).where(user.c.id == sa.bindparam("uid"))
    +)
    +
    +
    +

    These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:

    +
    engine = await gino.create_engine("postgresql://localhost/", bakery=bakery)
    +
    +
    +

    By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene.

    +

    To execute the baked queries, you could treat the BakedQuery +instances as if they are the queries themselves, for example:

    +
    now = await engine.scalar(db_time)
    +
    +
    +

    Pass in parameter values:

    +
    row = await engine.first(user_query, uid=123)
    +
    +
    +
    +
    +

    Use the Gino Integration

    +

    In a more common scenario, there will be a Gino instance, which has +usually a bind set - either explicitly or by the Web framework extensions:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        ...
    +
    +
    +

    A Bakery is automatically created in the db instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +db_time = db.bake("SELECT now()")
    +user_getter = db.bake(User.query.where(User.id == db.bindparam("uid")))
    +
    +
    +

    And the execution is also simplified with the same bind magic:

    +
    async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        print(await db_time.scalar())
    +
    +        user: User = await user_getter.first(uid=1)
    +        print(user.name)
    +
    +
    +

    To make things easier, you could even define the baked queries directly on the +model:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +    @db.bake
    +    def getter(cls):
    +        return cls.query.where(cls.id == db.bindparam("uid"))
    +
    +    @classmethod
    +    async def get(cls, uid):
    +        return await cls.getter.one_or_none(uid=uid)
    +
    +
    +

    Here GINO treats the getter() as a declared_attr() with +with_table=True, therefore it takes one positional argument cls for the User +class.

    +
    +
    +

    How to customize loaders?

    +

    If possible, you could bake the additional execution options into the query:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +)
    +
    +
    +

    The bake() method accepts keyword arguments as execution +options to e.g. simplify the example above into:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")),
    +    loader=User.load(comment="Added by loader."),
    +)
    +
    +
    +

    If the query construction is complex, bake() could also be +used as a decorator:

    +
    @db.bake
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +
    +
    +

    Or with short execution options:

    +
    @db.bake(loader=User.load(comment="Added by loader."))
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid"))
    +
    +
    +

    Meanwhile, it is also possible to override the loader at runtime:

    +
    user: User = await user_getter.load(User).first(uid=1)
    +print(user.name)  # no more comment on user!
    +
    +
    +
    +

    Hint

    +

    This override won’t affect the baked query - it’s used only in this execution.

    +
    +
    +
    +

    What APIs are available on BakedQuery?

    +

    BakedQuery is a GinoExecutor, so it inherited +all the APIs like all(), +first(), one(), +one_or_none(), scalar(), +status(), load(), +timeout(), etc.

    +

    GinoExecutor is actually the chained .gino helper API seen +usually in queries like this:

    +
    user = await User.query.where(User.id == 123).gino.first()
    +
    +
    +

    So a BakedQuery can be seen as a normal query with the .gino +suffix, plus it is directly executable.

    +
    +

    See also

    +

    Please see API document of gino.bakery for more information.

    +
    +
    +
    +

    I don’t want the prepared statements.

    +

    If you don’t need all the baked queries (m) to create prepared statements for all +the active database connections (n) in the beginning, you could set +prebake=False in the engine initialization to prevent the default initial +m x n prepare calls:

    +
    e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False)
    +
    +
    +

    Or if you’re using bind:

    +
    await db.set_bind("postgresql://...", prebake=False)
    +
    +
    +

    This is useful when you’re depending on db.gino.create_all() to create the tables, +because the prepared statements can only be created after the table creation.

    +

    The prepared statements will then be created and cached lazily on demand.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/contributing.html b/docs/en/1.1b2/how-to/contributing.html new file mode 100644 index 0000000..532068b --- /dev/null +++ b/docs/en/1.1b2/how-to/contributing.html @@ -0,0 +1,360 @@ + + + + + + + + Contributing - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Contributing

    +

    Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given.

    +

    You can contribute in many ways:

    +
    +

    Types of Contributions

    +
    +

    Report Bugs

    +

    Report bugs at https://github.com/python-gino/gino/issues.

    +

    If you are reporting a bug, please include:

    +
      +
    • Your operating system name and version.

    • +
    • Any details about your local setup that might be helpful in troubleshooting.

    • +
    • Detailed steps to reproduce the bug.

    • +
    +
    +
    +

    Fix Bugs

    +

    Look through the GitHub issues for bugs. Anything tagged with “bug” +and “help wanted” is open to whoever wants to implement it.

    +
    +
    +

    Implement Features

    +

    Look through the GitHub issues for features. Anything tagged with “enhancement” +and “help wanted” is open to whoever wants to implement it.

    +
    +
    +

    Write Documentation

    +

    GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such.

    +
    +
    +

    Submit Feedback

    +

    The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues.

    +

    If you are proposing a feature:

    +
      +
    • Explain in detail how it would work.

    • +
    • Keep the scope as narrow as possible, to make it easier to implement.

    • +
    • Remember that this is a volunteer-driven project, and that contributions +are welcome :)

    • +
    +
    +
    +
    +

    Get Started!

    +

    Ready to contribute? Here’s how to set up gino for local development.

    +
      +
    1. Fork the gino repo on GitHub.

    2. +
    3. Clone your fork locally:

      +
      $ git clone git@github.com:your_name_here/gino.git
      +
      +
      +
    4. +
    5. Create a branch for local development:

      +
      $ cd gino/
      +$ git checkout -b name-of-your-bugfix-or-feature
      +
      +
      +
    6. +
    +

    Now you can make your changes locally.

    +
      +
    1. Create virtual environment. Example for virtualenvwrapper:

      +
      $ mkvirtualenv gino
      +
      +
      +
    2. +
    3. Activate the environment and install requirements:

      +
      $ pip install -r requirements_dev.txt
      +
      +
      +
    4. +
    5. When you’re done making changes, check that your changes pass syntax checks:

      +
      $ flake8 gino tests
      +
      +
      +
    6. +
    +

    7. And tests (including other Python versions with tox). +For tests you you will need running database server (see “Tips” section below for configuration details):

    +
    $ pytest tests
    +$ tox
    +
    +
    +
      +
    1. For docs run:

      +
      $ make docs
      +
      +
      +
    2. +
    +

    It will build and open up docs in your browser.

    +
      +
    1. Commit your changes and push your branch to GitHub:

      +
      $ git add .
      +$ git commit -m "Your detailed description of your changes."
      +$ git push origin name-of-your-bugfix-or-feature
      +
      +
      +
    2. +
    3. Submit a pull request through the GitHub website.

    4. +
    +
    +
    +

    Pull Request Guidelines

    +

    Before you submit a pull request, check that it meets these guidelines:

    +
      +
    1. The pull request should include tests.

    2. +
    3. If the pull request adds functionality, the docs should be updated. Put +your new functionality into a function with a docstring, and add the +feature to the list in README.rst.

    4. +
    5. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check +https://github.com/python-gino/gino/actions?query=event%3Apull_request +and make sure that the tests pass for all supported Python versions.

    6. +
    +
    +
    +

    Tips

    +

    To run a subset of tests:

    +
    $ py.test -svx tests.test_gino
    +
    +
    +

    By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using ‘psql’ or similar:

    +
    CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino';
    +CREATE DATABASE gino WITH OWNER = gino;
    +
    +
    +

    Then run the tests like so:

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino
    +$ py.test
    +
    +
    +

    Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):

    +
    $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
    +$ openssl rsa -in privkey.pem -passin pass:abcd -out server.key
    +$ openssl req -x509 -in server.req -text -key server.key -out server.crt
    +$ chmod 600 server.key
    +$ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    +
    +
    +

    Terminal 2 (client):

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433
    +$ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'"
    +$ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;"
    +$ pytest tests/test_aiohttp.py
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/crud.html b/docs/en/1.1b2/how-to/crud.html new file mode 100644 index 0000000..44b0de7 --- /dev/null +++ b/docs/en/1.1b2/how-to/crud.html @@ -0,0 +1,195 @@ + + + + + + + + CRUD - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    CRUD

    +

    THIS IS A WIP

    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/faq.html b/docs/en/1.1b2/how-to/faq.html new file mode 100644 index 0000000..c6699e0 --- /dev/null +++ b/docs/en/1.1b2/how-to/faq.html @@ -0,0 +1,558 @@ + + + + + + + + Frequently Asked Questions - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Frequently Asked Questions

    +
    +

    SQLAlchemy 1.4 supports asyncio, what will GINO be?

    +

    Starting from 1.4, SQLAlchemy will support asyncio. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +greenlet. As an “async” user, you don’t +need to worry about its internals in most cases, it’s fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed.

    +

    Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features:

    +
      +
    • Contextual Connections (see Engine and Connection)

    • +
    • SQLAlchemy Core-based CRUD models

    • +
    • The GINO Loader system

    • +
    • Async MySQL support

    • +
    • Typing support NEW

    • +
    • Trio support NEW

    • +
    • Execution performance NEW

    • +
    +

    As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized.

    +
    +
    +

    ORM or not ORM?

    +

    GINO does perform the Object-Relational Mapping work under the +Data Mapper Pattern, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them “dirty” - or in a different way of thinking +they are always “dirty”. Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely non-ORM way.

    +
    +
    +

    Can I use features of SQLAlchemy ORM?

    +

    SQLAlchemy has two parts:

    +
      +
    • SQLAlchemy Core

    • +
    • SQLAlchemy ORM

    • +
    +

    GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won’t work in GINO.

    +
    +
    +

    How to join?

    +

    GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know how to write join in +SQLAlchemy. +Especially, Ádám made some amazing upgrades in +GINO #113 to make join easier, so +that you can use model classes directly as if they are tables in joining:

    +
    results = await User.join(Book).select().gino.all()
    +
    +
    +
    +
    +

    How to connect to database through SSL?

    +

    It depends on the dialect and database driver. For asyncpg, keyword arguments +on asyncpg.connect() are directly +available on create_engine() or db.set_bind(). Therefore, enabling SSL is rather easy:

    +
    engine = await gino.create_engine(..., ssl=True)
    +
    +
    +
    +
    +

    What is aiocontextvars and what does it do?

    +

    It is a partial backport of the new built-in module contextvars introduced in Python +3.7. In Python 3.5 and 3.6, aiocontextvars patches loop.create_task() +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2

    +

    If you are using Python 3.7, then aiocontextvars does nothing at all.

    +
    +

    Note

    +

    This answer is for GINO 0.8 and later, please check earlier versions of +this documentation if you are using GINO 0.7.

    +
    +
    +
    +

    How to define relationships?

    +

    GINO 1.0 or lower doesn’t provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see Loaders and Relationship for more information.

    +
    +
    +

    How to define index with multiple columns?

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    first_name = db.Column(db.Unicode())
    +    last_name = db.Column(db.Unicode())
    +
    +    _name_idx = db.Index('index_on_name', 'first_name', 'last_name')
    +
    +
    +

    The _name_idx is not used.

    +
    +
    +

    Is there a django admin interface for GINO?

    +

    Not quite yet, please follow this discussion.

    +
    +
    +

    How to use multiple databases for different users on the fly?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query.

    +

    In order to use multiple databases, you would need multiple +GinoEngine instances. Here’s a full example using FastAPI with +lazy engine creation:

    +
    from asyncio import Future
    +from contextvars import ContextVar
    +
    +from fastapi import FastAPI, Request
    +from gino import create_engine
    +from gino.ext.starlette import Gino
    +
    +engines = {}
    +dbname = ContextVar("dbname")
    +
    +
    +class ContextualGino(Gino):
    +    @property
    +    def bind(self):
    +        e = engines.get(dbname.get(""))
    +        if e and e.done():
    +            return e.result()
    +        else:
    +            return self._bind
    +
    +    @bind.setter
    +    def bind(self, val):
    +        self._bind = val
    +
    +
    +app = FastAPI()
    +db = ContextualGino(app)
    +
    +
    +@app.middleware("http")
    +async def lazy_engines(request: Request, call_next):
    +    name = request.query_params.get("db", "postgres")
    +    fut = engines.get(name)
    +    if fut is None:
    +        fut = engines[name] = Future()
    +        try:
    +            engine = await create_engine("postgresql://localhost/" + name)
    +        except Exception as e:
    +            fut.set_exception(e)
    +            del engines[name]
    +            raise
    +        else:
    +            fut.set_result(engine)
    +    await fut
    +    dbname.set(name)
    +    return await call_next(request)
    +
    +
    +@app.get("/")
    +async def get():
    +    return dict(dbname=await db.scalar("SELECT current_database()"))
    +
    +
    +
    +
    +

    How to load complex query results?

    +

    The API doc of gino.loader explains the available loaders, and there’re a few +examples in Loaders and Relationship too.

    +

    Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a TupleLoader with two sub-loaders - +ModelLoader and ColumnLoader:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Be ware of the tuple in .gino.load((...)).

    +
    +
    +

    How to do bulk or batch insert / update?

    +

    For a simple example, take a model that has one field, “name.” In your application you +have a list of names you would like to add to the database:

    +
    new_names = ["Austin", "Ali", "Jeff", "Marissa"]
    +
    +
    +

    To quickly insert the names in one query, first construct a dict with the +{"model_key": "value"} format:

    +
    new_names_dict = [dict(name=new_name) for new_name in new_names]
    +>> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}]
    +
    +
    +

    Finally, run an insert statement on the model:

    +
    await User.insert().gino.all(new_names_dict)
    +
    +
    +
    +
    +

    How to print the executed SQL?

    +

    GINO uses the same approach from SQLAlchemy: create_engine(..., echo=True). +(Or db.set_bind(..., echo=True)) Please see also here.

    +

    If you use any extension, you can also set that in config, by db_echo or DB_ECHO.

    +
    +
    +

    How to run EXISTS SQL?

    +
    await db.scalar(db.exists().where(User.email == email).select())
    +
    +
    +
    +
    +

    How to work with Alembic?

    +

    The fact that Gino is a MetaData is the +key to use Alembic. Just import and set target_metadata = db in Alembic env.py +will do. See Use Alembic for more details.

    +
    +
    +

    How to join the same table twice?

    +

    This is the same pattern as described in SQLAlchemy Adjacency List Relationships, where you +have a table with “a foreign key reference to itself”, or join the same table more than +once, “to represent hierarchical data in flat tables.” We’d need to use +alias(), for example:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.ForeignKey("users.id"))
    +
    +Parent = User.alias()
    +query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
    +users = await query.gino.load(User.load(parent=Parent)).all()
    +
    +
    +
    +
    +

    How to execute raw SQL with parameters?

    +

    Wrap the SQL with text(), and use keyword arguments:

    +
    query = db.text('SELECT * FROM users WHERE id = :id_val')
    +row = await db.first(query, id_val=1)
    +
    +
    +

    You may even load the rows into model instances:

    +
    query = query.execution_options(loader=User)
    +user = await db.first(query, id_val=1)
    +
    +
    +
    +
    +

    Gino engine is not initialized?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query. If bind is not set before +this, you’ll see this error:

    +
    gino.exceptions.UninitializedError: Gino engine is not initialized.
    +
    +
    +

    You could use either:

    +
      +
    • Call set_bind() or with_bind() to set the +bind on the Gino instance.

    • +
    • Use one of the Web framework extensions to set the bind for you in usually the server +start-up hook.

    • +
    • Use explicit bind for each execution, for example:

      +
      engine = await create_engine("...")
      +# ...
      +user = await User.get(request.user_id, bind=engine)
      +
      +
      +
    • +
    +
    +
    +

    How can I do SQL xxxx in GINO?

    +

    GINO uses SQLAlchemy Core queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy Table instances, and the column +attributes on GINO model classes are just SQLAlchemy Column +instances, you can use them in building your SQLAlchemy Core queries.

    +

    Alternatively, you could always execute the raw SQL directly, see How to execute raw SQL with parameters? above.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/json-props.html b/docs/en/1.1b2/how-to/json-props.html new file mode 100644 index 0000000..9277692 --- /dev/null +++ b/docs/en/1.1b2/how-to/json-props.html @@ -0,0 +1,376 @@ + + + + + + + + JSON Property - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    JSON Property

    +

    GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields.

    +
    +

    Quick Start

    +
    from gino import Gino
    +from sqlalchemy.dialects.postgresql import JSONB
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +    birthday = db.DateTimeProperty()
    +
    +
    +

    The age and birthday are JSON properties stored in the profile column. You +may use them the same way as a normal GINO model field:

    +
    u = await User.create(name="daisy", age=18)
    +print(u.name, u.age)  # daisy 18
    +
    +
    +
    +

    Note

    +

    profile is the default column name for all JSON properties in a model. If you +need a different column name for some JSON properties, you’ll need to specify +explicitly:

    +
    audit_profile = db.Column(JSON, nullable=False, server_default="{}")
    +
    +access_log = db.ArrayProperty(prop_name="audit_profile")
    +abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
    +
    +
    +
    +

    Using JSON properties in queries is supported:

    +
    await User.query.where(User.age > 16).gino.all()
    +
    +
    +

    This is simply translated into a native JSON query like this:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
    +
    +
    +

    Datetime type is very much the same:

    +
    from datetime import datetime
    +
    +await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
    +
    +
    +

    And the generated SQL:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
    +-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
    +
    +
    +

    Here’s a list of all the supported JSON properties:

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    JSON Property

    Python type

    JSON type

    Database Type

    StringProperty

    str

    string

    text

    IntegerProperty

    int

    number

    int

    BooleanProperty

    bool

    boolean

    boolean

    DateTimeProperty

    datetime

    string

    text

    ObjectProperty

    dict

    object

    JSON

    ArrayProperty

    list

    array

    JSON

    +
    +
    +

    Hooks

    +

    JSON property provides 2 instance-level hooks to customize the data:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @age.before_set
    +    def age(self, val):
    +        return val - 1
    +
    +    @age.after_get
    +    def age(self, val):
    +        return val + 1
    +
    +u = await User.create(name="daisy", age=18)
    +print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
    +
    +
    +

    And 1 class-level hook to customize the SQLAlchemy expression of the property:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    height = db.JSONProperty()
    +
    +    @height.expression
    +    def height(cls, exp):
    +        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
    +
    +
    +
    +
    +

    Create Index on JSON Properties

    +

    We’ll need to use declared_attr() to wait until the model class +is initialized. The rest is very much the same as defining a usual index:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @db.declared_attr
    +    def age_idx(cls):
    +        return db.Index("age_idx", cls.age)
    +
    +
    +

    This will lead to the SQL below executed if you run db.gino.create_all():

    +
    CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
    +
    +
    +
    +

    Warning

    +

    Alembic doesn’t support auto-generating revisions for functional indexes yet. You’ll +need to manually edit the revision. Please follow this issue for updates.

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/loaders.html b/docs/en/1.1b2/how-to/loaders.html new file mode 100644 index 0000000..d0b9433 --- /dev/null +++ b/docs/en/1.1b2/how-to/loaders.html @@ -0,0 +1,639 @@ + + + + + + + + Loaders and Relationship - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Loaders and Relationship

    +

    Loaders are used to load database row results into objects.

    +

    GINO doesn’t support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined.

    +
    +

    Model Loader

    +

    The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:

    +
    query = db.select([User])
    +rows = await query.gino.all()
    +
    +
    +

    In order to load rows into User objects, you can provide an execution +option loader with a new ModelLoader instance:

    +
    from gino.loader import ModelLoader
    +
    +query = db.select([User])
    +query = query.execution_options(loader=ModelLoader(User))
    +users = await query.gino.all()
    +
    +
    +

    The ModelLoader would then load each database row into a +User object. As this is frequently used, GINO made it a shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User.load())
    +users = await query.gino.all()
    +
    +
    +

    And another shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User)
    +users = await query.gino.all()
    +
    +
    +
    +

    Tip

    +

    User as loader is transformed into ModelLoader(User) by +Loader.get(), explained later in “Loader +Expression”.

    +
    +

    And again:

    +
    query = db.select([User])
    +users = await query.gino.load(User).all()
    +
    +
    +

    This is identical to the normal CRUD query:

    +
    users = await User.query.gino.all()
    +
    +
    +
    +
    +

    Loader Expression

    +

    So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than ModelLoader, +there’re also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders.

    +

    Here is an example using all loaders at once:

    +
    uid, user, sep, cols = await db.select([User]).gino.load(
    +    (
    +        User.id,
    +        User,
    +        '|',
    +        lambda row, ctx: len(row),
    +    )
    +).first()
    +
    +
    +

    Let’s check this piece by piece. Overall, the argument of +load() is a tuple. This is interpreted into a +TupleLoader, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a TupleLoader is a tuple.

    +

    Column in Loader Expressions are interpreted as +ColumnLoader. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, ColumnLoader uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use ColumnLoader, +you’ll have to declare columns for the query:

    +
    now = db.Column('time', db.DateTime())
    +result = await db.first(db.text(
    +    'SELECT now() AT TIME ZONE \'UTC\''
    +).columns(
    +    now,
    +).gino.load(
    +    ('now:', now)
    +).query)
    +print(*result)  # now: 2018-04-08 08:23:02.431847
    +
    +
    +

    Let’s get back to previous example. The second item in the tuple is a GINO +model class. As we’ve presented previously, it is interpreted into a +ModelLoader. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values.

    +
    +

    Tip

    +

    For a complex loader expression, the same row is given to all loaders, so +it doesn’t matter User.id is already used before the model loader.

    +
    +

    The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we’ll get to that later. Similar to +map(), the return value of the call will be the loaded result.

    +

    At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the '|' separator, it will show up as the third item +in every result returned by the query.

    +
    +
    +

    Many-to-One Relationship

    +

    A classic many-to-one relationship is also known as referencing - the model on +the “many” end keeps a single reference to the model on the “one” end. Although +GINO does not enforce it, usually people use a foreign key for the reference:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +

    So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:

    +
    async for child in Child.load(parent=Parent).gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    As you may have noticed, Child.load is exactly the shortcut to create +ModelLoader in the very first example. With some +additional keyword arguments, Child.load(parent=Parent) is still a +ModelLoader for Child, the model loader is at the +same time a query builder. It is identical to do this:

    +
    async for child in Child.load(parent=Parent).query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    The query dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The Loader simply forwarded unknown +attributes to its query, that’s why .query can +be omitted.

    +

    For ModelLoader, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using setattr(). For example, Parent is +interpreted as ModelLoader(Parent) which loads Parent instances, and +Parent instances are set as the parent attribute of the outer Child +instance.

    +
    +

    Warning

    +

    If multiple children references the same parent, then each child owns a +unique parent instance with identical values.

    +
    +
    +

    Tip

    +

    You don’t have to define parent attribute on Child. But if you do, +you gain the ability to customize how parent is stored or retrieved. For +example, let’s store the parent instance as _parent:

    +
    class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    _parent = None
    +
    +    @property
    +    def parent(self):
    +        return self._parent
    +
    +    @parent.setter
    +    def parent(self, value):
    +        self._parent = value
    +
    +
    +
    +

    The query builder works recursively. For ModelLoader, it +uses LEFT OUTER JOIN to connect the FROM clauses, in order to achieve +many-to-one scenario. The ON clause is determined automatically by foreign +keys. You can also customize the ON clause in case there is no foreign key +(a promise is a promise):

    +
    loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +async for child in loader.query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    And subloaders can be nested:

    +
    subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
    +
    +
    +

    By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values.

    +
    +
    +

    Self Referencing

    +
    +

    Warning

    +

    Experimental feature.

    +
    +

    Self referencing is usually used to create a tree-like structure. For example:

    +
    class Category(db.Model):
    +    __tablename__ = 'categories'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    +
    +
    +

    In order to load leaf categories with their parents, an alias is needed:

    +
    Parent = Category.alias()
    +
    +
    +

    Then the query would be something like this:

    +
    parents = db.select([Category.parent_id])
    +query = Category.load(parent=Parent.on(
    +    Category.parent_id == Parent.id
    +)).where(
    +    ~Category.id.in_(db.select([Category.alias().parent_id]))
    +)
    +async for c in query.gino.iterate():
    +    print(f'Leaf: {c.id}, Parent: {c.parent.id}')
    +
    +
    +

    The generated SQL looks like this:

    +
    SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id
    +  FROM categories LEFT OUTER JOIN categories AS categories_1
    +    ON categories.parent_id = categories_1.id
    + WHERE categories.id NOT IN (
    +           SELECT categories_2.parent_id
    +             FROM categories AS categories_2
    +       )
    +
    +
    +
    +
    +

    Other Relationships

    +

    GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    @children.setter
    +    def add_child(self, child):
    +        self._children.add(child)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child)).all()
    +
    +
    +

    Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the add_child setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +parent.add_child = new_child.

    +

    Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries.

    +

    GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you’ll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._child = None
    +
    +    @property
    +    def child(self):
    +        return self._child
    +
    +    @child.setter
    +    def child(self, child):
    +        self._child = child
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
    +
    +
    +

    Similarly, you can build many-to-many relationships in the same way:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    def add_child(self, child):
    +        self._children.add(child)
    +        child._parents.add(self)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._parents = set()
    +
    +    @property
    +    def parents(self):
    +        return self._parents
    +
    +
    +class ParentXChild(db.Model):
    +    __tablename__ = 'parents_x_children'
    +
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
    +
    +
    +query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
    +
    +
    +

    Likewise, there is for now no way to modify the relationships automatically, +you’ll have to manually create, delete or modify ParentXChild instances.

    +
    +
    +

    Advanced Usage of Loaders

    +

    You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For example, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Using alias to get ID-ascending pairs from the same table:

    +
    ua1 = User.alias()
    +ua2 = User.alias()
    +join_query = select([ua1, ua2]).where(ua1.id < ua2.id)
    +loader = ua1.load('id'), ua2.load('id')
    +result = await join_query.gino.load(loader).all()
    +print(result)  # e.g. [(1, 2), (1, 3), (2, 3)]
    +
    +
    +

    Potentially there could be a lot of different use cases of loaders. We’ll add +more inspiration here in the future.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/pool.html b/docs/en/1.1b2/how-to/pool.html new file mode 100644 index 0000000..436c5ff --- /dev/null +++ b/docs/en/1.1b2/how-to/pool.html @@ -0,0 +1,217 @@ + + + + + + + + Connection Pool - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Connection Pool

    +

    Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +NullPool), and users can define their own pools. +The base class should be Pool.

    +

    To use non-default pools in raw GINO:

    +
    from gino.dialects.asyncpg import NullPool
    +create_engine('postgresql://...', pool_class=NullPool)
    +
    +
    +

    To use non-default pools in extensions (taking Sanic as an example):

    +
    from gino.dialects.asyncpg import NullPool
    +from gino.ext.sanic import Gino
    +
    +app = sanic.Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_KWARGS = dict(
    +    pool_class=NullPool,
    +)
    +db = Gino()
    +db.init_app(app)
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/schema.html b/docs/en/1.1b2/how-to/schema.html new file mode 100644 index 0000000..5c618df --- /dev/null +++ b/docs/en/1.1b2/how-to/schema.html @@ -0,0 +1,419 @@ + + + + + + + + Schema Declaration - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Schema Declaration

    +

    There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy Table.

    +
    +

    GINO Engine

    +

    This is the minimized way to use GINO - using only +GinoEngine (and GinoConnection +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two.

    +

    For example, the table declaration is the same as SQLAlchemy core tutorial:

    +
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    +
    +metadata = MetaData()
    +
    +users = Table(
    +    'users', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('name', String),
    +    Column('fullname', String),
    +)
    +
    +addresses = Table(
    +    'addresses', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('user_id', None, ForeignKey('users.id')),
    +    Column('email_address', String, nullable=False)
    +)
    +
    +
    +
    +

    Note

    +

    When using GINO Engine only, it is usually your own business to create the +tables with either create_all() on a +normal non-async SQLAlchemy engine, or using Alembic. However it is still +possible to be done with GINO if it had to:

    +
    import gino
    +from gino.schema import GinoSchemaVisitor
    +
    +async def main():
    +    engine = await gino.create_engine('postgresql://...')
    +    await GinoSchemaVisitor(metadata).create_all(engine)
    +
    +
    +
    +

    Then, construct queries, in SQLAlchemy core too:

    +
    ins = users.insert().values(name='jack', fullname='Jack Jones')
    +
    +
    +

    So far, everything is still in SQLAlchemy. Now let’s get connected and execute +the insert:

    +
    async def main():
    +    engine = await gino.create_engine('postgresql://localhost/gino')
    +    conn = await engine.acquire()
    +    await conn.status(ins)
    +    print(await conn.all(users.select()))
    +    # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Here create_engine() creates a GinoEngine, +then acquire() checks out a +GinoConnection, and +status() executes the insert and returns the +status text. This works similarly as SQLAlchemy +execute() - they take the same parameters +but return a bit differently. There are also other similar query APIs:

    +
      +
    • all() returns a list of +RowProxy

    • +
    • first() returns one +RowProxy, or None

    • +
    • one() returns one +RowProxy

    • +
    • one_or_none() returns one +RowProxy, or None

    • +
    • scalar() returns a single value, or +None

    • +
    • iterate() returns an asynchronous iterator +which yields RowProxy

    • +
    +

    Please go to their API for more information.

    +
    +
    +

    GINO Core

    +

    In previous scenario, GinoEngine must not be set to +metadata.bind because it is not a +regular SQLAlchemy Engine thus it won’t work correctly. For this, GINO provides +a subclass of MetaData as Gino, +usually instantiated globally under the name of db. It can be used as a +normal MetaData still offering some conveniences:

    +
      +
    • It delegates most public types you can access on sqlalchemy

    • +
    • It works with both normal SQLAlchemy engine and asynchronous GINO engine

    • +
    • It exposes all query APIs on GinoConnection level

    • +
    • It injects two gino extensions on SQLAlchemy query clauses and schema +items, allowing short inline execution like users.select().gino.all()

    • +
    • It is also the entry for the third scenario, see later

    • +
    +

    Then we can achieve previous scenario with less code like this:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +users = db.Table(
    +    'users', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('name', db.String),
    +    db.Column('fullname', db.String),
    +)
    +
    +addresses = db.Table(
    +    'addresses', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('user_id', None, db.ForeignKey('users.id')),
    +    db.Column('email_address', db.String, nullable=False)
    +)
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await users.insert().values(
    +            name='jack',
    +            fullname='Jack Jones',
    +        ).gino.status()
    +        print(await users.select().gino.all())
    +        # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but sqlalchemy seems +never imported. This is useful when ORM is unwanted.

    +
    +

    Tip

    +

    asyncpgsa does the same thing, +but in a conceptually reversed way - instead of having asyncpg work for +SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that +way too because GINO is inspired by asyncpgsa). Either way works fine, it’s +just a matter of taste of whose API style to use, SQLAlchemy or asyncpg.

    +
    +
    +
    +

    GINO ORM

    +

    If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    fullname = db.Column(db.String)
    +
    +
    +class Address(db.Model):
    +    __tablename__ = 'addresses'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    user_id = db.Column(None, db.ForeignKey('users.id'))
    +    email_address = db.Column(db.String, nullable=False)
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await User.create(name='jack', fullname='Jack Jones')
    +        print(await User.query.gino.all())
    +        # Outputs: [<User object at 0x10a8ba860>]
    +
    +
    +
    +

    Important

    +

    The __tablename__ is a mandatory field to define a concrete model.

    +
    +

    As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in db. The class style is just +more declarative. Instead of users.c.name, you can now access the column by +User.name. The implicitly created Table is +available at User.__table__ and Address.__table__. You can use anything +that works in GINO core here.

    +
    +

    Note

    +

    Column names can be different as a class property and database column. +For example, name can be declared as +nickname = db.Column('name', db.Unicode(), default='noname'). In this +example, User.nickname is used to access the column, while in database, +the column name is name.

    +

    What’s worth mentioning is where raw SQL statements are used, or +TableClause is involved, like User.insert(), the original name is +required to be used, because in this case, GINO has no knowledge about the +mappings.

    +
    +
    +

    Tip

    +

    db.Model is a dynamically created parent class for your models. It is +associated with the db on initialization, therefore the table is put in +the very db when you declare your model class.

    +
    +

    Things become different when it comes to CRUD. You can use model level methods +to directly create() a model instance, instead of +inserting a new row. Or delete() a model instance +without needing to specify the where clause manually. Query returns model +instances instead of RowProxy, and row values are +directly available as attributes on model instances. See also: +CRUD.

    +

    After all, GinoEngine is always in use. Next let’s dig +more into it.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/how-to/transaction.html b/docs/en/1.1b2/how-to/transaction.html new file mode 100644 index 0000000..cf4e60e --- /dev/null +++ b/docs/en/1.1b2/how-to/transaction.html @@ -0,0 +1,303 @@ + + + + + + + + Transaction - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Transaction

    +

    It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an await will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it.

    +
    +

    Basic usage

    +

    Transactions belong to GinoConnection. The most common +way to use transactions is through an async with statement:

    +
    async with connection.transaction() as tx:
    +    await connection.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    This guarantees a transaction is opened when entering the async with block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at raw_transaction, but in +most cases you don’t need to touch it.

    +

    GINO provides two convenient shortcuts to end the transaction early:

    + +

    They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the async with block after +raise_commit() or +raise_rollback() is skipped. The +internal exception is inherited from BaseException so that normal try +... except Exception block can’t trap it. This exception stops propagating at +the end of async with block, so you don’t need to worry about handling it.

    +

    Transactions can also be started on a GinoEngine:

    +
    async with engine.transaction() as tx:
    +    await engine.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    Here a GinoConnection is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The GinoConnection instance is accessible at +tx.connection. Other than +that, everything else is the same.

    +
    +

    Important

    +

    The implicit connection is by default borrowed with reuse=True. That +means using transaction() of +GinoEngine within a connection context is the same as +calling transaction() of the current +connection without having to reference it, no separate connection shall be +created.

    +
    +

    Similarly, if your Gino instance has a bind, you may also do +the same on it:

    +
    async with db.transaction() as tx:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +
    +
    +

    Nested Transactions

    +

    Transactions can be nested, nested transaction will create a savepoint as for +now on asyncpg. A similar example from asyncpg doc:

    +
    async with connection.transaction() as tx1:
    +    await connection.status('CREATE TABLE mytab (a int)')
    +
    +    # Create a nested transaction:
    +    async with connection.transaction() as tx2:
    +        await connection.status('INSERT INTO mytab (a) VALUES (1), (2)')
    +        # Rollback the nested transaction:
    +        tx2.raise_rollback()
    +
    +    # Because the nested transaction was rolled back, there
    +    # will be nothing in `mytab`.
    +    assert await connection.all('SELECT a FROM mytab') == []
    +
    +
    +

    As you can see, the raise_rollback() +breaks only the async with block of the specified tx2, the outer +transaction tx1 just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won’t trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate.

    +

    Because of the default reusing behavior, transactions on engine or db +follows the same nesting rules. Please see +GinoTransaction for more information.

    +
    +
    +

    Manual Control

    +

    Other than using async with, you can also manually control the +transaction:

    +
    tx = await connection.transaction()
    +try:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    You can’t use raise_commit() or +raise_rollback() here, similarly it is +prohibited to use commit() and +rollback() in an async with block.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/index.html b/docs/en/1.1b2/index.html new file mode 100644 index 0000000..89527b2 --- /dev/null +++ b/docs/en/1.1b2/index.html @@ -0,0 +1,231 @@ + + + + + + + + Welcome to GINO’s documentation! - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Welcome to GINO’s documentation!

    +PyPI Release Version +GitHub Workflow Status for tests +Codacy coverage +Dependabot +

    GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy core for Python asyncio. Now (early 2020) GINO supports only one +dialect asyncpg.

    + + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/objects.inv b/docs/en/1.1b2/objects.inv new file mode 100644 index 0000000..fc6c595 Binary files /dev/null and b/docs/en/1.1b2/objects.inv differ diff --git a/docs/en/1.1b2/py-modindex.html b/docs/en/1.1b2/py-modindex.html new file mode 100644 index 0000000..18dcbf9 --- /dev/null +++ b/docs/en/1.1b2/py-modindex.html @@ -0,0 +1 @@ +

    domainindex.html

    \ No newline at end of file diff --git a/docs/en/1.1b2/reference.html b/docs/en/1.1b2/reference.html new file mode 100644 index 0000000..b2c81d6 --- /dev/null +++ b/docs/en/1.1b2/reference.html @@ -0,0 +1,336 @@ + + + + + + + + Reference - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Reference

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api.html b/docs/en/1.1b2/reference/api.html new file mode 100644 index 0000000..12c2124 --- /dev/null +++ b/docs/en/1.1b2/reference/api.html @@ -0,0 +1,242 @@ + + + + + + + + API Reference - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.aiocontextvars.html b/docs/en/1.1b2/reference/api/gino.aiocontextvars.html new file mode 100644 index 0000000..3d02f85 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.aiocontextvars.html @@ -0,0 +1,216 @@ + + + + + + + + gino.aiocontextvars module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.aiocontextvars module

    +
    +
    +gino.aiocontextvars.patch_asyncio()
    +

    Patches asyncio to support contextvars.

    +

    This is automatically called when gino is imported. If Python version is 3.7 +or greater, this function is a no-op.

    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.api.html b/docs/en/1.1b2/reference/api/gino.api.html new file mode 100644 index 0000000..b54603f --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.api.html @@ -0,0 +1,621 @@ + + + + + + + + gino.api module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.api module

    +
    +
    +class gino.api.Gino(bind=None, model_classes=None, query_ext=True, schema_ext=True, ext=True, **kwargs)
    +

    Bases: sqlalchemy.sql.schema.MetaData

    +

    All-in-one API class of GINO, providing several shortcuts.

    +

    This class is a subclass of SQLAlchemy +MetaData, therefore its instances can be used +as a normal MetaData object, e.g. used in +Alembic. In usual cases, you would +want to define one global Gino instance, usually under the name +of db, representing the database used in your application.

    +

    You may define tables in the official way +SQLAlchemy core recommended, but more often in GINO we define model classes +with db.Model as their parent class to represent tables, for its +objective interface and CRUD operations. Please read CRUD +for more information.

    +

    For convenience, Gino instance delegated all properties publicly +exposed by sqlalchemy, so that you can define tables / models +without importing sqlalchemy:

    +
    id = db.Column(db.BigInteger(), primary_key=True)
    +
    +
    +

    Similar to MetaData, a Gino object +can bind to a GinoEngine instance, hereby allowing +“implicit execution” through the gino +extension on Executable or +SchemaItem constructs:

    +
    await User.query.gino.first()
    +await db.gino.create_all()
    +
    +
    +

    Differently, GINO encourages the use of implicit execution and manages +transactional context correctly.

    +

    Binding to a connection object is not supported.

    +

    To set a bind property, you can simply set your +GinoEngine object on db.bind, or +set it to None to unbind. However, the creation of engine usually +happens at the same time. Therefore, GINO provided several convenient ways +doing so:

    +
      +
    1. with_bind() returning an asynchronous context manager:

      +
      async with db.with_bind('postgresql://...') as engine:
      +
      +
      +
    2. +
    3. set_bind() and pop_bind():

      +
      engine = await db.set_bind('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    4. +
    5. Directly await on Gino instance:

      +
      db = await gino.Gino('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    6. +
    +
    +

    Note

    +

    SQLAlchemy allows creating the engine by:

    +
    metadata.bind = 'postgresql://...'
    +
    +
    +

    While in GINO this only sets a string to bind, because +creating an engine requires await, which is exactly what +set_bind() does.

    +
    +

    At last, Gino delegates all query APIs on the bound +GinoEngine.

    +
    +
    +property Model
    +

    Declarative base class for models, subclass of +gino.declarative.Model. Defining subclasses of this class will +result new tables added to this Gino metadata.

    +
    + +
    +
    +acquire(*args, **kwargs)
    +

    A delegate of GinoEngine.acquire().

    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.all().

    +
    + +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    A delegate of Bakery.bake().

    +
    +

    New in version 1.1.

    +
    +
    + +
    +
    +property bakery
    +

    The bundled Bakery instance.

    +
    +

    New in version 1.1.

    +
    +
    + +
    +
    +property bind
    +

    An GinoEngine to which this Gino is bound.

    +

    This is a simple property with no getter or setter hook - what you set +is what you get. To achieve the same result as it is in SQLAlchemy - +setting a string or URL and getting an +engine instance, use set_bind() (or await on this +Gino object after setting a string or +URL).

    +
    + +
    +
    +compile(elem, *multiparams, **params)
    +

    A delegate of GinoEngine.compile().

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.first().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.iterate().

    +
    + +
    +
    +model_base_classes = (<class 'gino.crud.CRUDModel'>,)
    +

    Overridable default model classes to build the Model.

    +

    Default is (CRUDModel, ).

    +
    + +
    +
    +no_delegate = {'create_engine', 'engine_from_config'}
    +

    A set of symbols from sqlalchemy which is not delegated by +Gino.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one_or_none().

    +
    + +
    +
    +pop_bind()
    +

    Unbind self, and return the bound engine.

    +

    This is usually used in a chained call to close the engine:

    +
    await db.pop_bind().close()
    +
    +
    +
    +
    Returns
    +

    GinoEngine or None if self is not bound.

    +
    +
    +
    + +
    +
    +query_executor
    +

    The overridable gino extension class on +Executable.

    +

    This class will be set as the getter method of the property gino on +Executable and its subclasses, if +ext and query_ext arguments are both True. Default is +GinoExecutor.

    +

    alias of GinoExecutor

    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.scalar().

    +
    + +
    +
    +schema_visitor
    +

    alias of gino.schema.GinoSchemaVisitor

    +
    + +
    +
    +async set_bind(bind, loop=None, **kwargs)
    +

    Bind self to the given GinoEngine and return it.

    +

    If the given bind is a string or +URL, all arguments will be sent to +create_engine() to create a new engine, and return it.

    +
    +
    Returns
    +

    GinoEngine

    +
    +
    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.status().

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    A delegate of GinoEngine.transaction().

    +
    + +
    +
    +with_bind(bind, loop=None, **kwargs)
    +

    Shortcut for set_bind() and pop_bind() plus closing engine.

    +

    This method accepts the same arguments of +create_engine(). This allows inline creating an engine and +binding self on enter, and unbinding self and closing the engine on +exit:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # play with engine
    +
    +
    +
    +
    Returns
    +

    An asynchronous context manager.

    +
    +
    +
    + +
    + +
    +
    +class gino.api.GinoExecutor(query)
    +

    Bases: object

    +

    The default gino extension on +Executable constructs for implicit +execution.

    +

    Instances of this class are created when visiting the gino property of +Executable instances (also referred as +queries or clause elements), for example:

    +
    await User.query.gino.first()
    +
    +
    +

    This allows GINO to add the asynchronous query APIs (all(), +first(), one(), one_or_none(), scalar(), +status(), iterate()) to SQLAlchemy query clauses without +messing up with existing synchronous ones. +Calling these asynchronous query APIs has the same restriction - the +relevant metadata (the Gino instance) must be bound to an engine, +or an AttributeError will be raised.

    +
    +

    Note

    +

    Executable clause elements that are completely irrelevant with any +table - for example db.select([db.text('now()')]) - has no +metadata, hence no engine. Therefore, this will always fail:

    +
    await db.select([db.text('now()')]).gino.scalar()
    +
    +
    +

    You should use conn.scalar(), +engine.scalar() or even +db.scalar() in this case.

    +
    +
    +
    +async all(*multiparams, **params)
    +

    Returns engine.all() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +execution_options(**options)
    +

    Set execution options to this query in a chaining call.

    +

    Read execution_options() for more +information.

    +
    +
    Parameters
    +

    options – Multiple execution options.

    +
    +
    +
    +

    New in version 1.1.

    +
    +
    + +
    +
    +async first(*multiparams, **params)
    +

    Returns engine.first() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +iterate(*multiparams, **params)
    +

    Returns engine.iterate() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +load(value)
    +

    Shortcut to set execution option loader in a chaining call.

    +

    For example to load Book instances with their authors:

    +
    query = Book.join(User).select()
    +books = await query.gino.load(Book.load(author=User)).all()
    +
    +
    +

    Read execution_options() for more +information.

    +
    + +
    +
    +model(model)
    +

    Shortcut to set execution option model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async one(*multiparams, **params)
    +

    Returns engine.one() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async one_or_none(*multiparams, **params)
    +

    Returns engine.one_or_none() +with this query as the first argument, and other arguments followed, +where engine is the GinoEngine to which the +metadata (Gino) is bound, while metadata is found in this +query.

    +
    + +
    +
    +property query
    +

    Get back the chained Executable.

    +

    In a chained query calls, occasionally the previous query clause is +needed after a .gino. chain, you can use .query. to resume the +chain back. For example:

    +
    await User.query.gino.model(FOUser).query.where(...).gino.all()
    +
    +
    +
    + +
    +
    +return_model(switch)
    +

    Shortcut to set execution option return_model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async scalar(*multiparams, **params)
    +

    Returns engine.scalar() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async status(*multiparams, **params)
    +

    Returns engine.status() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +timeout(timeout)
    +

    Shortcut to set execution option timeout in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.bakery.html b/docs/en/1.1b2/reference/api/gino.bakery.html new file mode 100644 index 0000000..aea15e3 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.bakery.html @@ -0,0 +1,323 @@ + + + + + + + + gino.bakery module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.bakery module

    +
    +
    +class gino.bakery.BakedQuery(elem, metadata, hash_=None)
    +

    Bases: gino.api.GinoExecutor

    +

    Represents a pre-compiled and possibly prepared query for faster execution.

    +

    BakedQuery is created by Bakery.bake(), and can be executed by +GinoEngine or GinoConnection. If there +is a proper bind in the baked query, contextual execution APIs inherited from +GinoExecutor can also be used.

    +
    +

    New in version 1.1.

    +
    +
    +
    +property bind
    +

    Internal API to provide a proper bind if found.

    +
    + +
    +
    +property compiled_sql
    +

    Internal API to get the SQLAlchemy compiled sql context.

    +
    + +
    +
    +execution_options(**kwargs)
    +

    Set execution options on a shadow query of this baked query.

    +

    The execution options set in this method won’t affect the execution options in +the baked query.

    +

    Read execution_options() for more +information.

    +
    +
    Parameters
    +

    options – Multiple execution options.

    +
    +
    Returns
    +

    A shadow of the baked query with new execution options but still +functions as a baked query.

    +
    +
    +
    + +
    +
    +get(_)
    +

    Internal API to get the compiled_sql.

    +
    +
    Parameters
    +

    _ – Ignored.

    +
    +
    +
    + +
    +
    +property query
    +

    Internal API to get the query instance before compilation.

    +
    + +
    +
    +property sql
    +

    Internal API to get the compiled raw SQL.

    +
    + +
    + +
    +
    +class gino.bakery.Bakery
    +

    Bases: object

    +

    Factory and warehouse of baked queries.

    +

    You may provide a bakery to a GinoEngine during creation as +the bakery keyword argument, and the engine will bake the queries and create +corresponding prepared statements for each of the connections in the pool.

    +

    A Gino instance has a built-in bakery, +it’s automatically given to the engine during set_bind() or +with_bind().

    +
    +

    New in version 1.1.

    +
    +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    Bake a query.

    +

    You can bake raw SQL strings or SQLAlchemy Core query instances. This method +adds the given query into a queue in the bakery, and bakes it only when the +bakery is set to an GinoEngine from which the bakery could +learn about the SQL dialect and compile the queries into SQL. Once done, the +bakery is “closed”, you can neither give it to another engine, nor use it to +bake more queries.

    +
    +
    Parameters
    +
      +
    • func_or_elem – A str or a SQLAlchemy Core query instance, or a +function that returns such results.

    • +
    • execution_options – Shortcut to add SQLAlchemy execution options to the +query.

    • +
    +
    +
    Returns
    +

    A BakedQuery instance.

    +
    +
    +
    + +
    +
    +query_cls
    +

    alias of BakedQuery

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.crud.html b/docs/en/1.1b2/reference/api/gino.crud.html new file mode 100644 index 0000000..4124ac3 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.crud.html @@ -0,0 +1,653 @@ + + + + + + + + gino.crud module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.crud module

    +
    +
    +class gino.crud.Alias(model, *args, **kwargs)
    +

    Bases: object

    +

    Experimental proxy for table alias on model.

    +
    +
    +distinct(*columns)
    +
    + +
    +
    +load(*column_names, **relationships)
    +
    + +
    +
    +on(on_clause)
    +
    + +
    + +
    +
    +class gino.crud.CRUDModel(**values)
    +

    Bases: gino.declarative.Model

    +

    The base class for models with CRUD support.

    +

    Don’t inherit from this class directly, because it has no metadata. Use +db.Model instead.

    +
    +
    +classmethod alias(*args, **kwargs)
    +

    Experimental proxy for table alias on model.

    +
    + +
    +
    +append_where_primary_key(q)
    +

    Append where clause to locate this model instance by primary on the +given query, and return the new query.

    +

    This is mostly used internally in GINO, but also available for such +usage:

    +
    await user.append_where_primary_key(User.query).gino.first()
    +
    +
    +

    which is identical to:

    +
    await user.query.gino.first()
    +
    +
    +
    +

    Deprecated since version 0.7.6: Use lookup() instead.

    +
    +
    + +
    +
    +create(bind=None, timeout=<object object>, **values)
    +

    This create behaves a bit different on model classes compared to model +instances.

    +

    On model classes, create will create a new model instance and insert +it into database. On model instances, create will just insert the +instance into the database.

    +

    Under the hood create() uses INSERT ... RETURNING ... to +create the new model instance and load it with database default data if +not specified.

    +

    Some examples:

    +
    user1 = await User.create(name='fantix', age=32)
    +user2 = User(name='gino', age=42)
    +await user2.create()
    +
    +
    +
    +
    Parameters
    +
      +
    • bind – A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    • values – Keyword arguments are pairs of attribute names and their +initial values. Only available when called on a model class.

    • +
    +
    +
    Returns
    +

    The instance of this model class (newly created or existing).

    +
    +
    +
    + +
    +
    +delete
    +

    Similar to update(), this delete is also different on model +classes than on model instances.

    +

    On model classes delete is an attribute of type +Delete for massive deletes, for +example:

    +
    await User.delete.where(User.enabled.is_(False)).gino.status()
    +
    +
    +

    Similarly you can add a returning() +clause to the query and it shall return the deleted rows as model objects.

    +

    And on model instances, delete() is a method to remove the +corresponding row in the database of this model instance. and returns the +status returned from the database:

    +
    print(await user.delete())  # e.g. prints DELETE 1
    +
    +
    +
    +

    Note

    +

    delete() only removes the row from database, it does not affect the +current model instance.

    +
    +
    +
    Parameters
    +
      +
    • bind – An optional GinoEngine if current +metadata (Gino) has no bound engine, or specifying a +different GinoEngine to execute the DELETE.

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    +
    + +
    +
    +classmethod distinct(*columns)
    +

    Experimental loader feature to yield only distinct instances by given +columns.

    +
    + +
    +
    +async classmethod get(ident, bind=None, timeout=<object object>)
    +

    Get an instance of this model class by primary key.

    +

    For example:

    +
    user = await User.get(request.args.get('user_id'))
    +
    +
    +
    +
    Parameters
    +
      +
    • ident – Value of the primary key. For composite primary keys this +should be a tuple of values for all keys in database order, or a dict +of names (or position numbers in database order starting from zero) +of all primary keys to their values.

    • +
    • bind – A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    Returns
    +

    An instance of this model class, or None if no such row.

    +
    +
    +
    + +
    +
    +classmethod in_query(query)
    +

    Convenient method to get a Model object when using subqueries.

    +

    Though with filters and aggregations, subqueries often return same +columns as the original table, but SQLAlchemy could not recognize them +as the columns are in subqueries, so technically they’re columns in the +new “table”.

    +

    With this method, the columns are loaded into the original models when +being used in subqueries. For example:

    +
    query = query.alias('users')
    +MyUser = User.in_query(query)
    +
    +loader = MyUser.distinct(User1.id).load()
    +users = await query.gino.load(loader).all()
    +
    +
    +
    + +
    +
    +classmethod load(*column_names, **relationships)
    +

    Populates a loader.Loader instance to be used by the +loader execution option in order to customize the loading behavior +to load specified fields into instances of this model.

    +

    The basic usage of this method is to provide the loader execution +option (if you are looking for reloading +the instance from database, check get() or query) for a +given query.

    +

    This method takes both positional arguments and keyword arguments with +very different meanings. The positional arguments should be column +names as strings, specifying only these columns should be loaded into +the model instance (other values are discarded even if they are +retrieved from database). Meanwhile, the keyword arguments should be +loaders for instance attributes. For example:

    +
    u = await User.query.gino.load(User.load('id', 'name')).first()
    +
    +
    +
    +

    Tip

    +

    gino.load is a shortcut for setting the execution option +loader.

    +
    +

    This will populate a User instance with only id and name +values, all the rest are simply None even if the query actually +returned all the column values.

    +
    q = User.join(Team).select()
    +u = await q.gino.load(User.load(team=Team)).first()
    +
    +
    +

    This will load two instances of model User and Team, returning +the User instance with u.team set to the Team instance.

    +

    Both positional and keyword arguments can be used ath the same time. If +they are both omitted, like Team.load(), it is equivalent to just +Team as a loader.

    +

    Additionally, a loader.Loader instance can also be used to +generate queries, as its structure is usually the same as the query:

    +
    u = await User.load(team=Team).query.gino.first()
    +
    +
    +

    This generates a query like this:

    +
    SELECT users.xxx, ..., teams.xxx, ...
    +  FROM users LEFT JOIN teams
    +    ON ...
    +
    +
    +

    The Loader delegates attributes on the query, so +.query can be omitted. The LEFT JOIN is built-in behavior, +while the ON clause is generated based on foreign key. If there is +no foreign key, or the condition should be customized, you can use +this:

    +
    u = await User.load(
    +    team=Team.on(User.team_id == Team.id)).gino.first()
    +
    +
    +

    And you can use both load() and on() at the same time +in a chain, in whatever order suits you.

    +
    +

    See also

    +

    execution_options()

    +
    +
    + +
    +
    +lookup()
    +

    Generate where-clause expression to locate this model instance.

    +

    By default this method uses current values of all primary keys, and you +can override it to behave differently. Most instance-level CRUD +operations depend on this method internally. Particularly while +lookup() is called in update(), the where condition is +used in UpdateRequest.apply(), so that queries like UPDATE ... +SET id = NEW WHERE id = OLD could work correctly.

    +
    +
    Returns
    +

    +
    +
    +
    +

    New in version 0.7.6.

    +
    +
    + +
    +
    +classmethod none_as_none(enabled=True)
    +
    + +
    +
    +classmethod on(on_clause)
    +

    Customize the on-clause for the auto-generated outer join query.

    +
    +

    Note

    +

    This has no effect when provided as the loader execution option +for a given query.

    +
    +
    +

    See also

    +

    load()

    +
    +
    + +
    +
    +query
    +

    Get a SQLAlchemy query clause of the table behind this model. This equals +to sqlalchemy.select([self.__table__]). If this attribute is retrieved on a +model instance, then a where clause to locate this instance by its primary +key is appended to the returning query clause. This model type is set as +the execution option model in the returning clause, so by default the +query yields instances of this model instead of database rows.

    +
    + +
    +
    +select()
    +

    Build a query to retrieve only specified columns from this table.

    +

    This method accepts positional string arguments as names of attributes to +retrieve, and returns a Select for +query. The returning query object is always set with two execution options:

    +
      +
    1. model is set to this model type

    2. +
    3. return_model is set to False

    4. +
    +

    So that by default it always return rows instead of model instances, while +column types can be inferred correctly by the model option.

    +

    For example:

    +
    async for row in User.select('id', 'name').gino.iterate():
    +    print(row['id'], row['name'])
    +
    +
    +

    If select() is invoked on a model instance, then a WHERE clause +to locate this instance by its primary key is appended to the returning +query clause. This is useful when you want to retrieve a latest value of a +field on current model instance from database:

    +
    db_age = await user.select('age').gino.scalar()
    +
    +
    +
    +

    See also

    +

    execution_options()

    +
    +
    + +
    +
    +to_dict()
    +

    Convenient method to generate a dict from this model instance.

    +

    Keys will be attribute names, while values are loaded from memory (not +from database). If there are JSONProperty +attributes in this model, their source JSON field will not be included +in the returning dict - instead the JSON attributes will be.

    +
    +

    See also

    +

    json_support

    +
    +
    + +
    +
    +update
    +

    This update behaves quite different on model classes rather than model +instances.

    +

    On model classes, update is an attribute of type +Update for massive updates, for +example:

    +
    await User.update.values(enabled=True).where(...).gino.status()
    +
    +
    +

    Like query, the update query also has the model execution +option of this model, so if you use the +returning() clause, the query shall +return model objects.

    +

    However on model instances, update() is a method which accepts keyword +arguments only and returns an UpdateRequest to update this single +model instance. The keyword arguments are pairs of attribute names and new +values. This is the same as UpdateRequest.update(), feel free to +read more about it. A normal usage example would be like this:

    +
    await user.update(name='new name', age=32).apply()
    +
    +
    +

    Here, the await ... apply() executes the +actual UPDATE SQL in the database, while user.update() only makes +changes in the memory, and collect all changes into an +UpdateRequest instance.

    +
    + +
    + +
    +
    +class gino.crud.QueryModel
    +

    Bases: type

    +

    Metaclass of Model classes used for subqueries.

    +
    + +
    +
    +class gino.crud.UpdateRequest(instance: gino.crud.CRUDModel)
    +

    Bases: object

    +

    A collection of attributes and their new values to update on one model +instance.

    +

    UpdateRequest instances are created by CRUDModel.update, +don’t instantiate manually unless required. Every UpdateRequest +instance is bound to one model instance, all updates are for that one +specific model instance and its database row.

    +
    +
    +async apply(bind=None, timeout=<object object>)
    +

    Apply pending updates into database by executing an UPDATE SQL.

    +
    +
    Parameters
    +
      +
    • bind – A GinoEngine to execute the SQL, or +None (default) to use the bound engine in the metadata.

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    Returns
    +

    self for chaining calls.

    +
    +
    +
    + +
    +
    +update(**values)
    +

    Set given attributes on the bound model instance, and add them into +the update collections for apply().

    +

    Given keyword-only arguments are pairs of attribute names and values to +update. This is not a coroutine, calling update() will have +instant effect on the bound model instance - its in-memory values will +be updated immediately. Therefore this can be used individually as a +shortcut to update several attributes in a batch:

    +
    user.update(age=32, disabled=True)
    +
    +
    +

    update() returns self for chaining calls to either +apply() or another update(). If one attribute is updated +several times by the same UpdateRequest, then only the last +value is remembered for apply().

    +

    Updated values can be SQLAlchemy expressions, for example an atomic +increment for user balance looks like this:

    +
    await user.update(balance=User.balance + 100).apply()
    +
    +
    +
    +

    Note

    +

    Expression values will not affect the in-memory attribute value on +update() before apply(), because it has no knowledge +of the latest value in the database. After apply() the new +value will be automatically reloaded from database with +RETURNING clause.

    +
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.declarative.html b/docs/en/1.1b2/reference/api/gino.declarative.html new file mode 100644 index 0000000..3dd82df --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.declarative.html @@ -0,0 +1,401 @@ + + + + + + + + gino.declarative module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.declarative module

    +
    +
    +class gino.declarative.ColumnAttribute(prop_name, column)
    +

    Bases: object

    +

    The type of the column wrapper attributes on GINO models.

    +

    This is the core utility to enable GINO models so that:

    +
      +
    • Accessing a column attribute on a model class returns the column itself

    • +
    • Accessing a column attribute on a model instance returns the value for that column

    • +
    +

    This utility is customizable by defining __attr_factory__ in the model class.

    +
    + +
    +
    +class gino.declarative.InvertDict(*args, **kwargs)
    +

    Bases: dict

    +

    A custom dict that allows getting keys by values.

    +

    Used internally by Model.

    +
    +
    +invert_get(value, default=None)
    +

    Get key by value.

    +
    +
    Parameters
    +
      +
    • value – A value in this dict.

    • +
    • default – If specified value doesn’t exist, return default.

    • +
    +
    +
    Returns
    +

    The corresponding key if the value is found, or default otherwise.

    +
    +
    +
    + +
    + +
    +
    +class gino.declarative.Model
    +

    Bases: object

    +

    The base class of GINO models.

    +

    This is not supposed to be sub-classed directly, declarative_base() should +be used instead to generate a base model class with a given +MetaData. By defining subclasses of a model, instances +of sqlalchemy.schema.Table will be created and added to the bound +MetaData. The columns of the +Table instance are defined as +Column attributes:

    +
    from sqlalchemy import MetaData, Column, Integer, String
    +from gino.declarative import declarative_base
    +
    +Model = declarative_base()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = Column(Integer(), primary_key=True)
    +    name = Column(String())
    +
    +
    +

    The name of the columns are automatically set using the attribute name.

    +

    An instance of a model will maintain a memory storage for values of all the defined +column attributes. You can access these values by the same attribute name, or update +with new values, just like normal Python objects:

    +
    u = User()
    +assert u.name is None
    +
    +u.name = "daisy"
    +assert u.name == "daisy"
    +
    +
    +
    +

    Note

    +

    Accessing column attributes on a model instance will NOT trigger any database +operation.

    +
    +

    Constraint and Index are +also allowed as model class attributes. Their attribute names are not used.

    +

    A concrete model class can be used as a replacement of the +Table it reflects in SQLAlchemy queries. The model class +is also iterable, yielding all the Column instances +defined in the model.

    +

    Other built-in class attributes:

    +
      +
    • __metadata__

      +

      This is supposed to be set by declarative_base() and used only during +subclass construction. Still, this can be treated as a read-only attribute to +find out which MetaData this model is bound to.

      +
    • +
    • __tablename__

      +

      This is a required attribute to define a concrete model, meaning a +sqlalchemy.schema.Table instance will be created, added to the bound +MetaData and set to the class attribute __table__. +Not defining __tablename__ will result in an abstract model - no table +instance will be created, and instances of an abstract model are meaningless.

      +
    • +
    • __table__

      +

      This should usually be treated as an auto-generated read-only attribute storing +the sqlalchemy.schema.Table instance.

      +
    • +
    • __attr_factory__

      +

      An attribute factory that is used to wrap the actual +Column instance on the model class, so that the access +to the column attributes on model instances is redirected to the in-memory value +store. The default factory is ColumnAttribute, can be override.

      +
    • +
    • __values__

      +

      The internal in-memory value store as a dict, only available on model +instances. Accessing column attributes is equivalent to accessing __values__.

      +
    • +
    +
    + +
    +
    +gino.declarative.declarative_base(metadata, model_classes=(<class 'gino.declarative.Model'>, ), name='Model')
    +

    Create a base GINO model class for declarative table definition.

    +
    +
    Parameters
    +
      +
    • metadata – A MetaData instance to contain the +tables.

    • +
    • model_classes – Base class(es) of the base model class to be created. Default: +Model.

    • +
    • name – The class name of the base model class to be created. Default: +Model.

    • +
    +
    +
    Returns
    +

    A new base model class.

    +
    +
    +
    + +
    +
    +gino.declarative.declared_attr(m=None, *, with_table=False)
    +

    Mark a class-level method as a factory of attribute.

    +

    This is intended to be used as decorators on class-level methods of a +Model class. When initializing the class as well as its +subclasses, the decorated factory method will be called for each class, the +returned result will be set on the class in place of the factory method +under the same name.

    +

    @declared_attr is implemented differently than +declared_attr of SQLAlchemy, but they +are both more often used on mixins to dynamically declare indices or +constraints (also works for column and __table_args__, or even normal +class attributes):

    +
    class TrackedMixin:
    +    created = db.Column(db.DateTime(timezone=True))
    +
    +    @db.declared_attr
    +    def unique_id(cls):
    +        return db.Column(db.Integer())
    +
    +    @db.declared_attr
    +    def unique_constraint(cls):
    +        return db.UniqueConstraint('unique_id')
    +
    +    @db.declared_attr
    +    def poly(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.Column(db.Unicode())
    +
    +    @db.declared_attr
    +    def __table_args__(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.UniqueConstraint('poly'),
    +
    +
    +
    +

    Note

    +

    This doesn’t work if the model already had a __table__.

    +
    +
    +

    Changed in version 1.1: Added with_table parameter which works after the __table__ is created:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    ...
    +
    +    @db.declared_attr(with_table=True)
    +    def table_name(cls):
    +        # this is called only once when defining the class
    +        return cls.__table__.name
    +
    +assert User.table_name == "users"
    +
    +
    +
    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.dialects.aiomysql.html b/docs/en/1.1b2/reference/api/gino.dialects.aiomysql.html new file mode 100644 index 0000000..7283f39 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.dialects.aiomysql.html @@ -0,0 +1,588 @@ + + + + + + + + gino.dialects.aiomysql module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.aiomysql module

    +
    +
    +class gino.dialects.aiomysql.AiomysqlDBAPI
    +

    Bases: gino.dialects.base.BaseDBAPI

    +
    +
    +paramstyle = 'format'
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlDialect(*args, bakery=None, **kwargs)
    +

    Bases: sqlalchemy.dialects.mysql.base.MySQLDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.dialects.mysql.types._IntegerType'>: <class 'sqlalchemy.dialects.mysql.types._IntegerType'>, <class 'sqlalchemy.dialects.mysql.types._NumericType'>: <class 'sqlalchemy.dialects.mysql.types._NumericType'>, <class 'sqlalchemy.dialects.mysql.types._FloatType'>: <class 'sqlalchemy.dialects.mysql.types._FloatType'>, <class 'sqlalchemy.sql.sqltypes.Numeric'>: <class 'sqlalchemy.dialects.mysql.types.NUMERIC'>, <class 'sqlalchemy.sql.sqltypes.Float'>: <class 'sqlalchemy.dialects.mysql.types.FLOAT'>, <class 'sqlalchemy.sql.sqltypes.Time'>: <class 'sqlalchemy.dialects.mysql.types.TIME'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.MatchType'>: <class 'sqlalchemy.dialects.mysql.types._MatchType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.mysql.json.JSON'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONIndexType'>: <class 'sqlalchemy.dialects.mysql.json.JSONIndexType'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'sqlalchemy.dialects.mysql.json.JSONPathType'>, <class 'sqlalchemy.dialects.mysql.enumerated.ENUM'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.aiomysql.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of AiomysqlDBAPI

    +
    + +
    +
    +driver = 'aiomysql'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of AiomysqlExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given a DBAPI connection, return its isolation level.

    +

    When working with a _engine.Connection object, +the corresponding +DBAPI connection may be procured using the +_engine.Connection.connection accessor.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine isolation level facilities; +these APIs should be preferred for most typical use cases.

    +
    +

    See also

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +init_kwargs = {'auth_plugin', 'autocommit', 'bakery', 'charset', 'client_flag', 'connect_timeout', 'conv', 'cursorclass', 'db', 'host', 'init_command', 'local_infile', 'loop', 'maxsize', 'minsize', 'no_delay', 'password', 'pool_recycle', 'port', 'prebake', 'program_name', 'read_default_file', 'read_default_group', 'server_public_key', 'sql_mode', 'ssl', 'unix_socket', 'use_unicode', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument “conn” which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The “do_on_connect” callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    Returns
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    See also

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +postfetch_lastrowid = False
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given a DBAPI connection, set its isolation level.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine +isolation level facilities; these APIs should be preferred for +most typical use cases.

    +
    +

    See also

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +statement_compiler
    +

    alias of sqlalchemy.dialects.mysql.base.MySQLCompiler

    +
    + +
    +
    +support_prepare = False
    +
    + +
    +
    +support_returning = False
    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlExecutionContext
    +

    Bases: gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.mysql.base.MySQLExecutionContext

    +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +

    return self.cursor.lastrowid, or equivalent, after an INSERT.

    +

    This may involve calling special cursor functions, +issuing a new SELECT on the cursor (or a new one), +or returning a stored value that was +calculated within post_exec().

    +

    This function will only be called for dialects +which support “implicit” primary key generation, +keep preexecute_autoincrement_sequences set to False, +and when no explicit id value was bound to the +statement.

    +

    The function is called once, directly after +post_exec() and before the transaction is committed +or ResultProxy is generated. If the post_exec() +method assigns a value to self._lastrowid, the +value is used in place of calling get_lastrowid().

    +

    Note that this method is not equivalent to the +lastrowid method on ResultProxy, which is a +direct proxy to the DBAPI lastrowid accessor +in all cases.

    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlIterator(context, cursor)
    +

    Bases: gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AsyncEnum(*enums, **kw)
    +

    Bases: sqlalchemy.dialects.mysql.enumerated.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.DBAPICursor(dbapi_conn)
    +

    Bases: gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +iterate(context)
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.GinoNullType
    +

    Bases: sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +
      +
    • dialect – Dialect instance in use.

    • +
    • coltype – DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Pool(url, loop, init=None, bakery=None, prebake=True, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Transaction(conn, set_isolation=None)
    +

    Bases: gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.dialects.asyncpg.html b/docs/en/1.1b2/reference/api/gino.dialects.asyncpg.html new file mode 100644 index 0000000..f9f4ab4 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.dialects.asyncpg.html @@ -0,0 +1,598 @@ + + + + + + + + gino.dialects.asyncpg module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.asyncpg module

    +
    +
    +class gino.dialects.asyncpg.AsyncEnum(*enums, **kw)
    +

    Bases: sqlalchemy.dialects.postgresql.base.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCompiler(dialect, statement, column_keys=None, inline=False, **kwargs)
    +

    Bases: sqlalchemy.dialects.postgresql.base.PGCompiler

    +
    +
    +property bindtemplate
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCursor(context, cursor)
    +

    Bases: gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDBAPI
    +

    Bases: gino.dialects.base.BaseDBAPI

    +
    +
    +Error = (<class 'asyncpg.exceptions._base.PostgresError'>, <class 'asyncpg.exceptions._base.InterfaceError'>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDialect(*args, bakery=None, **kwargs)
    +

    Bases: sqlalchemy.dialects.postgresql.base.PGDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.sql.sqltypes.ARRAY'>: <class 'sqlalchemy.dialects.postgresql.array.ARRAY'>, <class 'sqlalchemy.sql.sqltypes.Interval'>: <class 'sqlalchemy.dialects.postgresql.base.INTERVAL'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'gino.dialects.asyncpg.AsyncpgJSONPathType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.postgresql.json.JSON'>, <class 'sqlalchemy.dialects.postgresql.base.ENUM'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.asyncpg.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of AsyncpgDBAPI

    +
    + +
    +
    +driver = 'asyncpg'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of AsyncpgExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given an asyncpg connection, return its isolation level.

    +
    + +
    +
    +async has_schema(connection, schema)
    +
    + +
    +
    +async has_sequence(connection, sequence_name, schema=None)
    +

    Check the existence of a particular sequence in the database.

    +

    Given a _engine.Connection object and a string +sequence_name, return True if the given sequence exists in +the database, False otherwise.

    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +async has_type(connection, type_name, schema=None)
    +
    + +
    +
    +init_kwargs = {'bakery', 'command_timeout', 'connection_class', 'database', 'host', 'init', 'loop', 'max_cacheable_statement_size', 'max_cached_statement_lifetime', 'max_inactive_connection_lifetime', 'max_queries', 'max_size', 'min_size', 'passfile', 'password', 'port', 'prebake', 'server_settings', 'setup', 'ssl', 'statement_cache_size', 'timeout', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument “conn” which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The “do_on_connect” callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    Returns
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    See also

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given an asyncpg connection, set its isolation level.

    +
    + +
    +
    +statement_compiler
    +

    alias of AsyncpgCompiler

    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgExecutionContext
    +

    Bases: gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.postgresql.base.PGExecutionContext

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgIterator(context, iterator)
    +

    Bases: object

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgJSONPathType
    +

    Bases: sqlalchemy.dialects.postgresql.json.JSONPathType

    +
    +
    +bind_processor(dialect)
    +

    Return a conversion function for processing bind values.

    +

    Returns a callable which will receive a bind parameter value +as the sole positional argument and will return a value to +send to the DB-API.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +

    dialect – Dialect instance in use.

    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.DBAPICursor(dbapi_conn)
    +

    Bases: gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.GinoNullType
    +

    Bases: sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +
      +
    • dialect – Dialect instance in use.

    • +
    • coltype – DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.NullPool(url, loop, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.Pool(url, loop, bakery=None, prebake=True, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.PreparedStatement(prepared, clause=None)
    +

    Bases: gino.dialects.base.PreparedStatement

    +
    + +
    +
    +class gino.dialects.asyncpg.Transaction(tx)
    +

    Bases: gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.dialects.base.html b/docs/en/1.1b2/reference/api/gino.dialects.base.html new file mode 100644 index 0000000..133cee8 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.dialects.base.html @@ -0,0 +1,482 @@ + + + + + + + + gino.dialects.base module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.base module

    +
    +
    +class gino.dialects.base.AsyncDialectMixin
    +

    Bases: object

    +
    +
    +compile(elem, *multiparams, **params)
    +
    + +
    +
    +cursor_cls
    +

    alias of DBAPICursor

    +
    + +
    +
    +classmethod dbapi()
    +
    + +
    +
    +dbapi_class
    +

    alias of BaseDBAPI

    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +support_prepare = True
    +
    + +
    +
    +support_returning = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.base.BaseDBAPI
    +

    Bases: object

    +
    +
    +static Binary(x)
    +
    + +
    +
    +Error
    +

    alias of builtins.Exception

    +
    + +
    +
    +paramstyle = 'numeric'
    +
    + +
    + +
    +
    +class gino.dialects.base.Cursor
    +

    Bases: object

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.base.DBAPICursor
    +

    Bases: object

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +execute(statement, parameters)
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +executemany(statement, parameters)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.base.ExecutionContextOverride
    +

    Bases: object

    +
    +
    +baked_query = None
    +
    + +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +
    + +
    +
    +get_result_proxy()
    +
    + +
    +
    +loader
    +
    + +
    +
    +model
    +
    + +
    +
    +process_rows(rows, return_model=True)
    +
    + +
    +
    +return_model
    +
    + +
    +
    +timeout
    +
    + +
    + +
    +
    +class gino.dialects.base.Pool
    +

    Bases: object

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.base.PreparedStatement(clause=None)
    +

    Bases: object

    +
    +
    +async all(*multiparams, **params)
    +
    + +
    +
    +async first(*multiparams, **params)
    +
    + +
    +
    +iterate(*params, **kwargs)
    +
    + +
    +
    +async scalar(*multiparams, **params)
    +
    + +
    +
    +async status(*multiparams, **params)
    +
    + +
    + +
    +
    +class gino.dialects.base.Transaction
    +

    Bases: object

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.dialects.html b/docs/en/1.1b2/reference/api/gino.dialects.html new file mode 100644 index 0000000..3efa8d1 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.dialects.html @@ -0,0 +1,227 @@ + + + + + + + + gino.dialects package - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects package

    + +
    +

    Module contents

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.engine.html b/docs/en/1.1b2/reference/api/gino.engine.html new file mode 100644 index 0000000..97e9986 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.engine.html @@ -0,0 +1,718 @@ + + + + + + + + gino.engine module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.engine module

    +
    +
    +class gino.engine.GinoConnection(dialect, sa_conn, stack=None)
    +

    Bases: object

    +

    Represents an actual database connection.

    +

    This is the root of all query API like all(), first(), +one(), one_or_none(), scalar() or status(), +those on engine or query are simply wrappers of methods in this class.

    +

    Usually instances of this class are created by GinoEngine.acquire().

    +
    +

    Note

    +

    GinoConnection may refer to zero or one underlying database +connection - when a GinoConnection is acquired with +lazy=True, the underlying connection may still be in the pool, +until a query API is called or get_raw_connection() is called.

    +

    Oppositely, one underlying database connection can be shared by many +GinoConnection instances when they are acquired with +reuse=True. The actual database connection is only returned to the +pool when the root GinoConnection is released. Read more +in GinoEngine.acquire() method.

    +
    +
    +

    See also

    +

    Engine and Connection

    +
    +
    +
    +async all(clause, *multiparams, **params)
    +

    Runs the given query in database, returns all results as a list.

    +

    This method accepts the same parameters taken by SQLAlchemy +execute(). You can pass in a raw +SQL string, or any SQLAlchemy query clauses.

    +

    If the given query clause is built by CRUD models, then the returning +rows will be turned into relevant model objects (Only one type of model +per query is supported for now, no relationship support yet). See +execution_options() for more information.

    +

    If the given parameters are parsed as “executemany” - bulk inserting +multiple rows in one call for example, the returning result from +database will be discarded and this method will return None.

    +
    + +
    +
    +property dialect
    +

    The Dialect in use, inherited +from the engine created this connection.

    +
    + +
    +
    +execution_options(**opt)
    +

    Set non-SQL options for the connection which take effect during +execution.

    +

    This method returns a copy of this GinoConnection which +references the same underlying database connection, but with the given +execution options set on the copy. Therefore, it is a good practice to +discard the copy immediately after use, for example:

    +
    row = await conn.execution_options(model=None).first(User.query)
    +
    +
    +

    This is very much the same as SQLAlchemy +execution_options(), it +actually does pass the execution options to the underlying SQLAlchemy +Connection. Furthermore, GINO added a +few execution options:

    +
    +
    Parameters
    +
      +
    • return_model – Boolean to control whether the returning results +should be loaded into model instances, where the model class is +defined in another execution option model. Default is True.

    • +
    • model – Specifies the type of model instance to create on return. +This has no effect if return_model is set to False. Usually +in queries built by CRUD models, this execution option is +automatically set. For now, GINO only supports loading each row into +one type of model object, relationships are not supported. Please use +multiple queries for that. None for no postprocessing (default).

    • +
    • timeout – Seconds to wait for the query to finish. None for +no time out (default).

    • +
    • loader

      A loader expression to load the database rows into +specified objective structure. It can be either:

      +
        +
      • A model class, so that the query will yield model instances of this +class. It is your responsibility to make sure all the columns of +this model is selected in the query.

      • +
      • A Column instance, so that each result +will be only a single value of this column. Please note, if you +want to achieve fetching the very first value, you should use +first() instead of +scalar(). However, using directly +scalar() is a more direct way.

      • +
      • A tuple nesting more loader expressions recursively.

      • +
      • A callable() function that will be called for each row to +fully customize the result. Two positional arguments will be passed +to the function: the first is the row instance, the second is a context +object which is only present if nested else None.

      • +
      • A Loader instance directly.

      • +
      • Anything else will be treated as literal values thus returned as +whatever they are.

      • +
      +

    • +
    +
    +
    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +async get_raw_connection(*, timeout=None)
    +

    Get the underlying database connection, acquire one if none present.

    +
    +
    Parameters
    +

    timeout – Seconds to wait for the underlying acquiring

    +
    +
    Returns
    +

    Underlying database connection instance depending on the +dialect in use

    +
    +
    Raises
    +

    TimeoutError if the acquiring timed out

    +
    +
    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    Cursors must work within transactions:

    +
    async with conn.transaction():
    +    async for user in conn.iterate(User.query):
    +        # handle each user without loading all users into memory
    +
    +
    +

    Alternatively, you can manually control how the cursor works:

    +
    async with conn.transaction():
    +    cursor = await conn.iterate(User.query)
    +    user = await cursor.next()
    +    users = await cursor.many(10)
    +
    +
    +

    Read more about how Cursor works.

    +

    Similarly, this method takes the same parameters as all().

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs the given query in database, returns exactly one result.

    +

    If the query returns no result, this method will raise +NoResultFound. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs the given query in database, returns at most one result.

    +

    If the query returns no result, this method will return None. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async prepare(clause)
    +
    + +
    +
    +property raw_connection
    +

    The current underlying database connection instance, type depends on +the dialect in use. May be None if self is a lazy connection.

    +
    + +
    +
    +async release(*, permanent=True)
    +

    Returns the underlying database connection to its pool.

    +

    If permanent=False, this connection will be set in lazy mode with +underlying database connection returned, the next query on this +connection will cause a new database connection acquired. This is +useful when this connection may still be useful again later, while some +long-running I/O operations are about to take place, which should not +take up one database connection or even transaction for that long time.

    +

    Otherwise with permanent=True (default), this connection will be +marked as closed after returning to pool, and be no longer usable +again.

    +

    If this connection is a reusing connection, then only this connection +is closed (depending on permanent), the reused underlying +connection will not be returned back to the pool.

    +

    Practically it is recommended to return connections in the reversed +order as they are borrowed, but if this connection is a reused +connection with still other opening connections reusing it, then on +release the underlying connection will be returned to the pool, +with all the reusing connections losing an available underlying +connection. The availability of further operations on those reusing +connections depends on the given permanent value.

    +
    +

    See also

    +

    GinoEngine.acquire()

    +
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +schema_for_object = <sqlalchemy.sql.schema._SchemaTranslateMap object>
    +

    A SQLAlchemy compatibility attribute, don’t use it for now, it bites.

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the query status.

    +

    The returning query status depends on underlying database and the +dialect in use. For asyncpg it is a string, you can parse it like this: +https://git.io/v7oze

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    Starts a database transaction.

    +

    There are two ways using this method: managed as an asynchronous +context manager:

    +
    async with conn.transaction() as tx:
    +    # run query in transaction
    +
    +
    +

    or manually awaited:

    +
    tx = await conn.transaction()
    +try:
    +    # run query in transaction
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    Where the tx is an instance of the +GinoTransaction class, feel free to read +more about it.

    +

    In the first managed mode, the transaction is automatically committed +on exiting the context block, or rolled back if an exception was raised +which led to the exit of the context. In the second manual mode, you’ll +need to manually call the +commit() or +rollback() methods on need.

    +

    If this is a lazy connection, entering a transaction will cause a new +database connection acquired if none was present.

    +

    Transactions may support nesting depending on the dialect in use. For +example in asyncpg, starting a second transaction on the same +connection will create a save point in the database.

    +

    For now, the parameters are directly passed to underlying database +driver, read asyncpg.connection.Connection.transaction() for +asyncpg.

    +
    + +
    + +
    +
    +class gino.engine.GinoEngine(dialect, pool, loop, logging_name=None, echo=None, execution_options=None)
    +

    Bases: object

    +

    Connects a Pool and +Dialect together to provide a source +of database connectivity and behavior.

    +

    A GinoEngine object is instantiated publicly using the +gino.create_engine() function or +db.set_bind() method.

    +
    +

    See also

    +

    Engine and Connection

    +
    +
    +
    +acquire(*, timeout=None, reuse=False, lazy=False, reusable=True)
    +

    Acquire a connection from the pool.

    +

    There are two ways using this method - as an asynchronous context +manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    which will guarantee the connection is returned to the pool when +leaving the async with block; or as a coroutine:

    +
    conn = await engine.acquire()
    +try:
    +    # play with the connection
    +finally:
    +    await conn.release()
    +
    +
    +

    where the connection should be manually returned to the pool with +conn.release().

    +

    Within the same context (usually the same Task, see +also Transaction), a nesting acquire by default re

    +
    +
    Parameters
    +
      +
    • timeout – Block up to timeout seconds until there is one free +connection in the pool. Default is None - block forever until +succeeded. This has no effect when lazy=True, and depends on the +actual situation when reuse=True.

    • +
    • reuse – Reuse the latest reusable acquired connection (before +it’s returned to the pool) in current context if there is one, or +borrow a new one if none present. Default is False for always +borrow a new one. This is useful when you are in a nested method call +series, wishing to use the same connection without passing it around +as parameters. See also: Transaction. A reusing +connection is not reusable even if reusable=True. If the reused +connection happened to be a lazy one, then the reusing connection is +lazy too.

    • +
    • lazy – Don’t acquire the actual underlying connection yet - do it +only when needed. Default is False for always do it immediately. +This is useful before entering a code block which may or may not make +use of a given connection object. Feeding in a lazy connection will +save the borrow-return job if the connection is never used. If +setting reuse=True at the same time, then the reused connection - +if any - applies the same laziness. For example, reusing a lazy +connection with lazy=False will cause the reused connection to +acquire an underlying connection immediately.

    • +
    • reusable – Mark this connection as reusable or otherwise. This +has no effect if it is a reusing connection. All reusable connections +are placed in a stack, any reusing acquire operation will always +reuse the top (latest) reusable connection. One reusable connection +may be reused by several reusing connections - they all share one +same underlying connection. Acquiring a connection with +reusable=False and reusing=False makes it a cleanly isolated +connection which is only referenced once here.

    • +
    +
    +
    Returns
    +

    A GinoConnection object.

    +
    +
    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    Acquires a connection with reuse=True and runs +all() on it. reuse=True means you can safely +do this without borrowing more than one underlying connection:

    +
    async with engine.acquire():
    +    await engine.all('SELECT ...')
    +
    +
    +

    The same applies for other query methods.

    +
    + +
    +
    +async close()
    +

    Close the engine, by closing the underlying pool.

    +
    + +
    +
    +compile(clause, *multiparams, **params)
    +

    A shortcut for compile() on +the dialect, returns raw SQL string and parameters according to the +rules of the dialect.

    +
    + +
    +
    +connection_cls
    +

    Customizes the connection class to use, default is +GinoConnection.

    +

    alias of GinoConnection

    +
    + +
    +
    +property current_connection
    +

    Gets the most recently acquired reusable connection in the context. +None if there is no such connection.

    +
    +
    Returns
    +

    GinoConnection

    +
    +
    +
    + +
    +
    +property dialect
    +

    Read-only property for the +Dialect of this engine.

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs first(), See all().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    This requires that there is a reusable connection in the current +context, and an active transaction is present. Then its +GinoConnection.iterate() is executed and returned.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs one(), See all().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs one_or_none(), See all().

    +
    + +
    +
    +property raw_pool
    +

    Read-only access to the underlying database connection pool instance. +This depends on the actual dialect in use, Pool +of asyncpg for example.

    +
    + +
    +
    +repr(color=False)
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs scalar(), See all().

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs status(). See also all().

    +
    + +
    +
    +transaction(*args, timeout=None, reuse=True, reusable=True, **kwargs)
    +

    Borrows a new connection and starts a transaction with it.

    +

    Different to GinoConnection.transaction(), transaction on engine +level supports only managed usage:

    +
    async with engine.transaction() as tx:
    +    # play with transaction here
    +
    +
    +

    Where the implicitly acquired connection is available as +tx.connection.

    +

    By default, transaction() acquires connection with +reuse=True and reusable=True, that means it by default tries to +create a nested transaction instead of a new transaction on a new +connection. You can change the default behavior by setting these two +arguments.

    +

    The other arguments are the same as +transaction() on connection.

    + +
    +
    Returns
    +

    A asynchronous context manager that yields a +GinoTransaction

    +
    +
    +
    + +
    +
    +update_execution_options(**opt)
    +

    Update the default execution_options dictionary +of this GinoEngine.

    + +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.exceptions.html b/docs/en/1.1b2/reference/api/gino.exceptions.html new file mode 100644 index 0000000..a2ae8e3 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.exceptions.html @@ -0,0 +1,250 @@ + + + + + + + + gino.exceptions module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.exceptions module

    +
    +
    +exception gino.exceptions.GinoException
    +

    Bases: Exception

    +
    + +
    +
    +exception gino.exceptions.InitializedError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.MultipleResultsFound
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoResultFound
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoSuchRowError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UninitializedError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UnknownJSONPropertyError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.ext.html b/docs/en/1.1b2/reference/api/gino.ext.html new file mode 100644 index 0000000..bcf694f --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.ext.html @@ -0,0 +1,227 @@ + + + + + + + + gino.ext package - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.ext package

    +
    +

    Module contents

    +

    Namespace package for GINO extensions.

    +

    This namespace package didn’t use any of the 3 official solutions. Instead, +gino.ext adds a hook into sys.meta_path, and utilize the Entry points +to find the extensions.

    +

    Any GINO extension package should provide an entry point like this:

    +
    [gino.extensions]
    +starlette = gino_starlette
    +
    +
    +

    So that the Python package gino_starlette will also be importable through +gino.ext.starlette.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.html b/docs/en/1.1b2/reference/api/gino.html new file mode 100644 index 0000000..6075ac8 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.html @@ -0,0 +1,280 @@ + + + + + + + + gino package - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino package

    + + +
    +

    Module contents

    +
    +
    +gino.create_engine(*args, **kwargs)
    +

    Shortcut for sqlalchemy.create_engine() with strategy="gino".

    +
    +

    Changed in version 1.1: Added the bakery keyword argument, please see Bakery.

    +
    +
    +

    Changed in version 1.1: Added the prebake keyword argument to choose when to create the prepared +statements for the queries in the bakery:

    +
      +
    • Pre-bake immediately when connected to the database (default).

    • +
    • No pre-bake but create prepared statements lazily when needed for the first +time.

    • +
    +

    Note: prebake has no effect in aiomysql

    +
    +
    + +
    +
    +gino.get_version()
    +

    Get current GINO version.

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.json_support.html b/docs/en/1.1b2/reference/api/gino.json_support.html new file mode 100644 index 0000000..6c185ee --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.json_support.html @@ -0,0 +1,350 @@ + + + + + + + + gino.json_support module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.json_support module

    +
    +
    +class gino.json_support.ArrayProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.BooleanProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.DateTimeProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.IntegerProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.JSONProperty(default=None, prop_name='profile')
    +

    Bases: object

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +get_profile(instance)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    +
    +reload(instance)
    +
    + +
    +
    +save(instance, value=<object object>)
    +
    + +
    + +
    +
    +class gino.json_support.ObjectProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.StringProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.loader.html b/docs/en/1.1b2/reference/api/gino.loader.html new file mode 100644 index 0000000..f692fcd --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.loader.html @@ -0,0 +1,633 @@ + + + + + + + + gino.loader module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.loader module

    +
    +
    +class gino.loader.AliasLoader(alias, *columns, **extras)
    +

    Bases: gino.loader.ModelLoader

    +

    The same as ModelLoader, kept for compatibility.

    +
    + +
    +
    +class gino.loader.CallableLoader(func)
    +

    Bases: gino.loader.Loader

    +

    Load the row by calling a specified function.

    +
    +
    Parameters
    +

    func – A callable() taking 2 positional arguments (row, context) +that will be called in do_load(), returning the loaded result.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to the given function.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    The result calling the given function, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ColumnLoader(column)
    +

    Bases: gino.loader.Loader

    +

    Load a given column in the row.

    +
    +
    Parameters
    +

    column – The column name as str, or a +Column instance to avoid name conflict.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – Not used.

    • +
    +
    +
    Returns
    +

    The value of the specified column, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.Loader
    +

    Bases: object

    +

    The abstract base class of loaders.

    +

    Loaders are used to load raw database rows into expected results. +GinoEngine will use the loader set on the loader value of +the execution_options(), for example:

    +
    from sqlalchemy import text, create_engine
    +from gino.loader import ColumnLoader
    +
    +e = await create_engine("postgresql://localhost/gino", strategy="gino")
    +q = text("SELECT now() as ts")
    +loader = ColumnLoader("ts")
    +ts = await e.first(q.execution_options(loader=loader))  # datetime
    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    Must be implemented in subclasses.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    Any result that the loader is supposed to return, followed by a boolean +value indicating if the result is distinct.

    +
    +
    +
    + +
    +
    +classmethod get(value)
    +

    Automatically create a loader based on the type of the given value.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    value type

    loader type

    tuple

    TupleLoader

    callable()

    CallableLoader

    Column, +Label

    ColumnLoader

    Model

    ModelLoader

    Alias

    AliasLoader

    Loader

    as is

    any other types

    ValueLoader

    +
    +
    Parameters
    +

    value – Any supported value above.

    +
    +
    Returns
    +

    A loader instance.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +property query
    +

    Generate a query from this loader.

    +

    This is an experimental feature, not all loaders support this.

    +
    +
    Returns
    +

    A query instance with the loader execution option set to self.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ModelLoader(model, *columns, **extras)
    +

    Bases: gino.loader.Loader

    +

    A loader that loads a row into a GINO model instance.

    +

    This loader generates an instance of the given model type and fills the instance +with attributes according to the other given parameters:

    +
      +
    • Load each column attribute listed in the given columns positional arguments.

    • +
    • If columns is not given, all defined columns of the model will be loaded.

    • +
    • For each keyword argument, its value will be used to generate a loader using +Loader.get(), and the loaded value will be setattr() to the model +instance under the name of the key.

    • +
    +

    For example, the simplest select and load:

    +
    sqlalchemy.select([User]).execution_options(loader=ModelLoader(User))
    +
    +
    +

    Select only the name column, and still load it into model instances:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, User.name)
    +)
    +
    +
    +

    This would also yield User instances, with all column attributes as None but +name.

    +

    Nest a ValueLoader:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, id=1)
    +)
    +
    +
    +

    1 is then converted into a ValueLoader and mocked the id attribute +of all returned User instances as 1.

    +

    Nest another ModelLoader:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company)
    +)
    +
    +
    +

    Likewise, Company is converted into a ModelLoader to load the +Company columns from the joined result, and the Company instances are set to +the company attribute of each User instance using setattr().

    +
    +
    Parameters
    +
      +
    • model – A subclass of Model to instantiate.

    • +
    • columns – A list of Column or str to +load, default is all the columns in the model.

    • +
    • extras – Additional attributes to load on the model instance.

    • +
    +
    +
    +
    +
    +distinct(*columns)
    +

    Configure this loader to reuse instances that have the same values of all the +give columns.

    +
    +
    Parameters
    +

    columns – Preferably Column instances.

    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    The model instance, followed by a boolean value indicating if the +result is distinct.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +load(*columns, **extras)
    +

    Update the loader with new rules.

    +

    After initialization, the rules of this loader can still be updated. This is +useful when using the model class as a shortcut of ModelLoader where +possible, chaining with a load() to initialize the rules, for example:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company.load('name'))
    +)
    +
    +
    +
    +
    Parameters
    +
      +
    • columns – If provided, replace the columns to load with the given ones.

    • +
    • extras – Update the loader with new extras.

    • +
    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +none_as_none(enabled=True)
    +

    Deprecated method for compatibility, does nothing.

    +
    + +
    +
    +on(on_clause)
    +

    Specify the on_clause for generating joined queries.

    +

    This is an experimental feature, used by get_from().

    +
    +
    Parameters
    +

    on_clause – An expression to feed into +join().

    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.TupleLoader(values)
    +

    Bases: gino.loader.Loader

    +

    Load multiple values into a tuple.

    +
    +
    Parameters
    +

    values – A tuple, each item is converted into a loader with +Loader.get().

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to sub-loaders.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    A tuple with loaded results from all sub-loaders, followed by +True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ValueLoader(value)
    +

    Bases: gino.loader.Loader

    +

    A loader that always return the specified value.

    +
    +
    Parameters
    +

    value – The value to return on load.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – Not used.

    • +
    • context – Not used.

    • +
    +
    +
    Returns
    +

    The given value, followed by True.

    +
    +
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.schema.html b/docs/en/1.1b2/reference/api/gino.schema.html new file mode 100644 index 0000000..b2f0132 --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.schema.html @@ -0,0 +1,328 @@ + + + + + + + + gino.schema module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.schema module

    +
    +
    +class gino.schema.AsyncSchemaDropper(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    Bases: gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaDropper

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, drop_ok=False)
    +
    + +
    +
    +async visit_table(table, drop_ok=False, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaGenerator(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    Bases: gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaGenerator

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, create_ok=False)
    +
    + +
    +
    +async visit_table(table, create_ok=False, include_foreign_key_constraints=None, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaTypeMixin
    +

    Bases: object

    +
    +
    +async create_async(bind=None, checkfirst=False)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncVisitor
    +

    Bases: object

    +
    +
    +async traverse_single(obj, **kw)
    +
    + +
    + +
    +
    +class gino.schema.GinoSchemaVisitor(item)
    +

    Bases: object

    +
    +
    +async create(bind=None, *args, **kwargs)
    +
    + +
    +
    +async create_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    +
    +async drop(bind=None, *args, **kwargs)
    +
    + +
    +
    +async drop_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    + +
    +
    +gino.schema.patch_schema(db)
    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.strategies.html b/docs/en/1.1b2/reference/api/gino.strategies.html new file mode 100644 index 0000000..f4e78dd --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.strategies.html @@ -0,0 +1,236 @@ + + + + + + + + gino.strategies module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.strategies module

    +
    +
    +class gino.strategies.GinoStrategy
    +

    Bases: sqlalchemy.engine.strategies.EngineStrategy

    +

    A SQLAlchemy engine strategy for GINO.

    +

    This strategy is initialized automatically as gino is imported.

    +

    If sqlalchemy.create_engine() uses strategy="gino", it will return a +Coroutine, and treat URL prefix postgresql:// or +postgres:// as postgresql+asyncpg://.

    +
    +
    +async create(name_or_url, loop=None, **kwargs)
    +

    Given arguments, returns a new Engine instance.

    +
    + +
    +
    +engine_cls
    +

    alias of gino.engine.GinoEngine

    +
    + +
    +
    +name = 'gino'
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/api/gino.transaction.html b/docs/en/1.1b2/reference/api/gino.transaction.html new file mode 100644 index 0000000..8ccd16e --- /dev/null +++ b/docs/en/1.1b2/reference/api/gino.transaction.html @@ -0,0 +1,332 @@ + + + + + + + + gino.transaction module - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.transaction module

    +
    +
    +class gino.transaction.GinoTransaction(conn, args, kwargs)
    +

    Bases: object

    +

    Represents an underlying database transaction and its connection, offering +methods to manage this transaction.

    +

    GinoTransaction is supposed to be created by either +gino.engine.GinoConnection.transaction(), or +gino.engine.GinoEngine.transaction(), or +gino.api.Gino.transaction(), shown as follows:

    +
    async with db.transaction() as tx:
    +    ...
    +
    +async with engine.transaction() as tx:
    +    ...
    +
    +async with conn.transaction() as tx:
    +    ...
    +
    +tx = await conn.transaction()
    +try:
    +    ...
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    When in use with asynchronous context manager, GinoTransaction +will be in managed mode, while the last example with await will put +the GinoTransaction in manual mode where you have to call +the commit() or rollback() to manually close the transaction.

    +

    In managed mode the transaction will be automatically committed or +rolled back on exiting the async with block depending on whether there +is an exception or not. Meanwhile, you can explicitly exit the transaction +early by raise_commit() or raise_rollback() which will raise +an internal exception managed by the asynchronous context manager and +interpreted as a commit or rollback action. In a nested transaction +situation, the two exit-early methods always close up the very transaction +which the two methods are referenced upon - all children transactions are +either committed or rolled back correspondingly, while no parent +transaction was ever touched. For example:

    +
    async with db.transaction() as tx1:
    +    async with db.transaction() as tx2:
    +        async with db.transaction() as tx3:
    +            tx2.raise_rollback()
    +            # Won't reach here
    +        # Won't reach here
    +    # Continues here with tx1, with both tx2 and tx3 rolled back.
    +
    +    # For PostgreSQL, tx1 can still be committed successfully because
    +    # tx2 and tx3 are just SAVEPOINTs in transaction tx1
    +
    +
    +
    +

    Tip

    +

    The internal exception raised from raise_commit() and +raise_rollback() is a subclass of BaseException, so +normal try ... except Exception: can’t trap the commit or rollback.

    +
    +
    +
    +async commit()
    +

    Only available in manual mode: manually commit this transaction.

    +
    + +
    +
    +property connection
    +

    Accesses to the GinoConnection of this +transaction. This is useful if when the transaction is started from +db or engine where the connection is implicitly acquired for +you together with the transaction.

    +
    + +
    +
    +raise_commit()
    +

    Only available in managed mode: skip rest of the code in this +transaction and commit immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    async with db.transaction() as tx:
    +    await user.update(age=64).apply()
    +    tx.raise_commit()
    +    await user.update(age=32).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +raise_rollback()
    +

    Only available in managed mode: skip rest of the code in this +transaction and rollback immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    assert user.age == 64  # assumption
    +
    +async with db.transaction() as tx:
    +    await user.update(age=32).apply()
    +    tx.raise_rollback()
    +    await user.update(age=128).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +property raw_transaction
    +

    Accesses to the underlying transaction object, whose type depends on +the dialect in use.

    +
    + +
    +
    +async rollback()
    +

    Only available in manual mode: manually rollback this transaction.

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/extensions.html b/docs/en/1.1b2/reference/extensions.html new file mode 100644 index 0000000..0d99f3c --- /dev/null +++ b/docs/en/1.1b2/reference/extensions.html @@ -0,0 +1,220 @@ + + + + + + + + Extensions - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/extensions/sanic.html b/docs/en/1.1b2/reference/extensions/sanic.html new file mode 100644 index 0000000..a904741 --- /dev/null +++ b/docs/en/1.1b2/reference/extensions/sanic.html @@ -0,0 +1,329 @@ + + + + + + + + Sanic Support - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/extensions/starlette.html b/docs/en/1.1b2/reference/extensions/starlette.html new file mode 100644 index 0000000..7faa63d --- /dev/null +++ b/docs/en/1.1b2/reference/extensions/starlette.html @@ -0,0 +1,326 @@ + + + + + + + + Starlette Support - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    Note

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/extensions/tornado.html b/docs/en/1.1b2/reference/extensions/tornado.html new file mode 100644 index 0000000..9e26b7a --- /dev/null +++ b/docs/en/1.1b2/reference/extensions/tornado.html @@ -0,0 +1,208 @@ + + + + + + + + Tornado Support - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/reference/history.html b/docs/en/1.1b2/reference/history.html new file mode 100644 index 0000000..00b4efc --- /dev/null +++ b/docs/en/1.1b2/reference/history.html @@ -0,0 +1,918 @@ + + + + + + + + History - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    History

    +
    +

    GINO 1.1

    +
    +

    1.1.0 (pending)

    +
      +
    • Added baked query feature (#478 #659 #667)

    • +
    • Added Query.gino.execution_options shortcut (#659)

    • +
    • Added @db.declared_attr(with_table=True) (#659)

    • +
    +
    +
    +
    +

    GINO 1.0

    +
    +

    Migrating to GINO 1.0

    +

    GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you’re using one of them, you should install GINO 1.0 with extras:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +

    Extension Module

    Installation in GINO 1.0

    gino.ext.starlette

    pip install gino[starlette]

    gino.ext.aiohttp

    pip install gino[aiohttp]

    gino.ext.sanic

    pip install gino[sanic]

    gino.ext.tornado

    pip install gino[tornado]

    gino.ext.quart

    pip install gino[quart]

    +

    The new extension packages are backward-compatible, so there’s no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed gino[starlette]:

    +
    from gino.ext.starlette import Gino
    +
    +
    +

    GINO 1.0 switched to Poetry for package and +dependency management and started to use the +src layout. This shouldn’t +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process:

    +
      +
    • Source files are now located under src directory.

    • +
    • The src dist on PyPI does not include tests, docs and some other files due to +a limitation of Poetry.

    • +
    +
    +
    +

    1.0.1 (2020-06-08)

    +
      +
    • Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4)

    • +
    • Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672)

    • +
    • Multiple JSON property fixes (#661 #662 #695)

    • +
    • Fixed extension typing issue (#673 #674)

    • +
    • Fixed model override behavior (#694)

    • +
    • Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696)

    • +
    +
    +
    +

    1.0.0 (2020-04-26)

    +
      +
    • Switched to Poetry for package and dependency management.

    • +
    • [Breaking] Moved built-in extension modules to separate PyPI packages.

    • +
    • Switched to src layout.

    • +
    • Switched to black code style.

    • +
    • Better documentation.

    • +
    • [Breaking] none_as_none() is now always enabled.

    • +
    • Added representation method for engine.

    • +
    • Protected the URL instance fed to set_bind() from manipulation.

    • +
    • Replaced some assert with AssertionError (#258 #655)

    • +
    +
    +
    +
    +

    GINO 0.8

    +

    This is also version 1.0 release candidate.

    +
    +

    Migrating to GINO 0.8

    +
    +

    1. contextvars

    +

    We introduced aiocontextvars 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the enable_inherit() or +disable_inherit() calls, because they are the default behavior now thus +no longer exist. However, you’ll need to confirm that the event loop in use is +always created after importing gino or aiocontextvars, or the patch +won’t work correctly.

    +

    There is nothing to worry about in Python 3.7.

    +
    +
    +

    2. none_as_none

    +

    When GINO tries to load a row with all NULL values into an instance, it +will now by default return None instead of an instance with all None +attributes. To recover the default behavior of 0.7, please specify +none_as_none(False) in affected model loader.

    +

    This is especially applicable to relationship sub-loaders - if the sub-loader +found it all NULL, no instance will be set to parent instance. For +example:

    +
    child = await Child.load(parent=Parent).query.gino.first()
    +
    +
    +

    If child.parent_id is NULL in database, then the child instance +won’t be called with any setattr(child, 'parent', ...) at all. (If you need +child.parent == None in this case, consider setting default value +parent = None in child model.)

    +

    Please note, it is deprecated to disable none_as_none, and disabling will +be removed in GINO 1.0.

    +
    +
    +
    +

    0.8.7 (2020-04-19)

    +
      +
    • Improved error handling when attribute names collide (Contributed by Reskov in #637 #638)

    • +
    • Fixed with_bind usability in aiohttp extension (#518)

    • +
    +
    +
    +

    0.8.6 (2020-02-10)

    +
      +
    • Fixed JSONPathType bind processor for asyncpg (#609)

    • +
    • Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600)

    • +
    • Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629)

    • +
    • Added distinct() to Alias (#628)

    • +
    +
    +
    +

    0.8.5 (2019-11-19)

    +
      +
    • Improved support for __tablename__ in declared_attr (Contributed by Roald Storm in #592)

    • +
    +
    +
    +

    0.8.4 (2019-11-09)

    +
      +
    • Better loader support for models in subqueries (#573 #585)

    • +
    • Allowed __tablename__ to be a declared_attr (#579 #582)

    • +
    • Fixed Sanic 19.9.0 compatibility (#569)

    • +
    • Added one() and one_or_none() (Contributed by Ilaï Deutel in #577)

    • +
    • Improved Starleet extension compatibility (Contributed by Jim O’Brien in #538)

    • +
    • Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533)

    • +
    • Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520)

    • +
    • Fixed Grammar (Contributed by Simeon J Morgan in #504)

    • +
    +
    +
    +

    0.8.3 (2019-06-06)

    +
      +
    • Fixed deprecated warnings in asyncpg dialect and aiohttp (#425)

    • +
    • Customizable db attribute name in aiohttp app instance (#457)

    • +
    • Added Starlette support (#486)

    • +
    +
    +
    +

    0.8.2 (2019-03-07)

    +
      +
    • Added exception for unknown JSON properties (#406 #408)

    • +
    • Supported Quart 0.7 (#411)

    • +
    • Accepted kwargs for db init in extensions (#407 #427)

    • +
    • Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440)

    • +
    • Added NullPool (#437 #441)

    • +
    • Unpinned dependency versions (#447)

    • +
    • Added support for SQLAlchemy 1.3 (#433 #451)

    • +
    +
    +
    +

    0.8.1 (2018-12-08)

    +
      +
    • Alias supported Label (#365)

    • +
    • Docs update (#308, 4c59ad, #401 by Pascal van Kooten)

    • +
    • Version requirement for SQLAlchemy is updated to >=1.2 (#378 #382)

    • +
    • Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) +* And all other extensions (#395)

    • +
    • Supported Tornado 5 (#396, also thanks to Vladimir Goncharov)

    • +
    • Fixed custom JSON/JSONB type support (#402 #403)

    • +
    +

    (Most fixes done by Tony Wang)

    +
    +
    +

    0.8.0 (2018-10-16)

    +
      +
    • Welcome Tony Wang to the maintenance team (#335)

    • +
    • Allowed custom column names (#261 #297)

    • +
    • Allowed column instance in model.load() (Contributed by Jekel in #323)

    • +
    • [Breaking] Upgraded to aiocontextvars 0.2.0 (#333)

    • +
    • Fixed bug that the same empty stack is shared between sub-tasks (#313 #334)

    • +
    • [Breaking] Made none_as_none() the default behavior (#351)

    • +
    • Bug fixes and docs update

    • +
    +
    +
    +
    +

    GINO 0.7

    +

    This is also version 1.0 beta 3.

    +
    +

    0.7.7 (2018-12-08)

    +
      +
    • Backported fix for custom JSON/JSONB type support (#402 #403)

    • +
    +
    +
    +

    0.7.6 (2018-08-26)

    +
      +
    • Updated library support (Contributed by Tony Wang in #275 #309)

    • +
    • Added none_as_none() (#281 #282)

    • +
    • Added ARRAY alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289)

    • +
    • Added Model.lookup() to prevent updating whole table without primary key (#287 #288)

    • +
    • Added DB_ECHO in extension options (Contributed by Mykyta Holubakha in #298)

    • +
    • Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305)

    • +
    • Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304)

    • +
    • Fixed to raise UninitializedError if bind is None (Contributed by Tony Wang in #307 #310)

    • +
    +
    +
    +

    0.7.5 (2018-07-26)

    +
      +
    • Added friendly error message when using abstract models by mistake (#224)

    • +
    • Supported Python 3.7 (Contributed by Tony Wang in #265)

    • +
    • Updated documentation

    • +
    • Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280)

    • +
    +
    +
    +

    0.7.4 (2018-06-10)

    +
      +
    • Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228)

    • +
    • Added Quart support (#213)

    • +
    • Fixed Tornado options parsing (#231)

    • +
    • Improved coding style and test coverage

    • +
    +
    +
    +

    0.7.3 (2018-05-19)

    +
      +
    • Fix for failing binary type (#225)

    • +
    +
    +
    +

    0.7.2 (2018-05-15)

    +
      +
    • Added prepared statement support (#14)

    • +
    • Added dsn in extension config (Contributed by Yurii Shtrikker in #215)

    • +
    +
    +
    +

    0.7.1 (2018-05-03)

    +
      +
    • Added support for inline model constraints (Contributed by Kinware in #198)

    • +
    • Added docs and tests for using SSL (#202)

    • +
    • Added declared_attr (#204)

    • +
    • Allowed ModelLoader passively load partial model (#216)

    • +
    +
    +
    +

    0.7.0 (2018-04-18)

    +
      +
    • Added Python 3.5 support (#187)

    • +
    • Added support to use dict as ident for Model.get (#192)

    • +
    • Added result loader (partial relationship support) (#13)

    • +
    • Added documentation on relationship and transaction (#146)

    • +
    +
    +
    +
    +

    GINO 0.6

    +

    This is also version 1.0 beta 2.

    +
    +

    Migrating to GINO 0.6

    +
    +

    1. Task Local

    +

    We created a new Python package aiocontextvars from previous local.py. If +you made use of the task local features, you should install this package.

    +

    Previous gino.enable_task_local() and gino.disable_task_local() are +replaced by aiocontextvars.enable_inherit() and +aiocontextvars.disable_inherit(). However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars by default offers task +local even without enable_inherit(), which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars is installed.

    +

    There is no gino.get_local() and gino.reset_local() relevant in +aiocontextvars. The similar thing is aiocontextvars.ContextVar instance +through its get(), set() and delete() methods.

    +

    Previous gino.is_local_root() is now +not aiocontextvars.Context.current().inherited.

    +
    +
    +

    2. Engine

    +

    GINO 0.6 hides asyncpg.Pool behind the new SQLAlchemy-alike +gino.GinoEngine. Instead of doing this in 0.5:

    +
    async with db.create_pool('postgresql://...') as pool:
    +    # your code here
    +
    +
    +

    You should change it to this in 0.6:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +

    This equals to:

    +
    engine = await gino.create_engine('postgresql://...')
    +db.bind = engine
    +try:
    +    # your code here
    +finally:
    +    db.bind = None
    +    await engine.close()
    +
    +
    +

    Or:

    +
    engine = await db.set_bind('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Or even this:

    +
    db = await gino.Gino('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Choose whichever suits you the best.

    +

    Obviously GinoEngine doesn’t provide asyncpg.Pool methods directly any +longer, but you can get the underlying asyncpg.Pool object through +engine.raw_pool property.

    +

    GinoPool.get_current_connection() is now changed to current_connection +property on GinoEngine instances to support multiple engines.

    +

    GinoPool.execution_option is gone, instead update_execution_options() +on GinoEngine instance is available.

    +

    GinoPool().metadata is gone, dialect is still available.

    +

    GinoPool.release() is removed in GinoEngine and Gino, the +release() method on GinoConnection object should be used instead.

    +

    These methods exist both in 0.5 GinoPool and 0.6 GinoEngine: +close(), acquire(), all(), first(), scalar(), status().

    +
    +
    +

    3. GinoConnection

    +

    Similarly, GinoConnection in 0.6 is no longer a subclass of +asyncpg.Connection, instead it has a asyncpg.Connection instance, +accessable through GinoConnection.raw_connection property.

    +

    GinoConnection.metadata is deleted in 0.6, while dialect remained.

    +

    GinoConnection.execution_options() is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior.

    +

    GinoConnection.release() is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +permanent=False to remain its previous behavior.

    +

    And all(), first(), scalar(), status(), iterate(), +transaction() remained in 0.6.

    +
    +
    +

    4. Query API

    +

    All five query APIs all(), first(), scalar(), status(), +iterate() now accept the same parameters as SQLAlchemy execute(), +meaning they accept raw SQL text, or multiple sets of parameters for +“executemany”. Please note, if the parameters are recognized as “executemany”, +none of the methods will return anything. Meanwhile, they no longer accept the +parameter bind if they did. Just use the API on the GinoEngine or +GinoConnection object instead.

    +
    +
    +

    5. Transaction

    +

    Transaction interface is rewritten. Now in 0.6, a GinoTransaction object is +provided consistently from all 3 methods:

    +
    async with db.transaction() as tx:
    +    # within transaction
    +
    +async with engine.transaction() as tx:
    +    # within transaction
    +
    +async with engine.acquire() as conn:
    +    async with conn.transaction() as tx:
    +        # within transaction
    +
    +
    +

    And different usage with await:

    +
    tx = await db.transaction()
    +try:
    +    # within transaction
    +    await tx.commit()
    +except:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    The GinoConnection object is available at tx.connection, while +underlying transaction object from database driver is available at +tx.transaction - for asyncpg it is an asyncpg.transaction.Transaction +object.

    +
    +
    +
    +

    0.6.6 (2018-05-18)

    +
      +
    • Backported a fix for failing binary type (#225)

    • +
    +
    +
    +

    0.6.5 (2018-04-18)

    +
      +
    • Abandoned 0.6.4 and keep 0.6.x stable

    • +
    • Backported doc for transaction

    • +
    +
    +
    +

    0.6.4 (2018-04-16)

    +

    Abandoned version, please use 0.7.0 instead.

    +
    +
    +

    0.6.3 (2018-04-08)

    +
      +
    • Added aiohttp support

    • +
    • Added support for calling create() on model instances (Contributed by Kinware in #178 #180)

    • +
    • Fixed get() by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184)

    • +
    +
    +
    +

    0.6.2 (2018-03-24)

    +
      +
    • Fixed SQLAlchemy prefetch issue (#141)

    • +
    • Fixed issue that mixin class on Model not working (#174)

    • +
    • Added more documentation (Thanks Olaf Conradi for reviewing)

    • +
    +
    +
    +

    0.6.1 (2018-03-18)

    +
      +
    • Fixed create and drop for Enum type (#160)

    • +
    • A bit more documentation (#159)

    • +
    +
    +
    +

    0.6.0 (2018-03-14)

    +
      +
    • [Breaking] API Refactored, Pool replaced with Engine

      +
        +
      • New API Engine replaced asyncpg Pool (#59)

      • +
      • Supported different dialects, theoretically

      • +
      • Used aiocontextvars instead of builtin task local (#89)

      • +
      +
    • +
    • [Breaking] Fixed query API with multiparams (executemany) to return correctly (#20)

    • +
    • [Breaking] The query methods no longer accept the parameter bind

    • +
    • [Breaking] Gino no longer exposes postgresql types

    • +
    • Added echo on engine (#142)

    • +
    • Added tests to cover 80% of code

    • +
    • Added gino extension on SchemaItem for create_all and so on (#76 #106)

    • +
    • Added gino extension on model classes for create() or drop()

    • +
    • Added _update_request_cls on CRUDModel (#147)

    • +
    • Rewrote the documentation (#146)

    • +
    +
    +
    +
    +

    GINO 0.5

    +

    This is also version 1.0 beta 1.

    +
    +

    0.5.8 (2018-02-14)

    +
      +
    • Preparing for 0.6.0 which will be a breaking release

    • +
    • Fixed wrong value of Enum in creation (Contributed by Sergey Kovalev in #126)

    • +
    +
    +
    +

    0.5.7 (2017-11-24)

    +

    This is an emergency fix for 0.5.6.

    +
      +
    • Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114)

    • +
    • Added Model.outerjoin

    • +
    +
    +
    +

    0.5.6 (2017-11-23)

    +
      +
    • Changed to use unnamed statement when possible (#80 #90)

    • +
    • Added more example (Contributed by Kentoseth in #109)

    • +
    • Added Model.join and made Model selectable (Contributed by Ádám Barancsuk in #112 #113)

    • +
    +
    +
    +

    0.5.5 (2017-10-18)

    +
      +
    • Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87)

    • +
    • Added ability to reset local storage (#84)

    • +
    • Fixed bug in JSON property update

    • +
    • Added update chaining feature

    • +
    +
    +
    +

    0.5.4 (2017-10-04)

    +
      +
    • Updated example (Contributed by Kinware in #75)

    • +
    • Added Model.insert (Contributed by Neal Wang in #63)

    • +
    • Fixed issue that non-lazy acquiring fails dirty (#79)

    • +
    +
    +
    +

    0.5.3 (2017-09-23)

    +
      +
    • Fixed no module named cutils error (Contributed by Vladimir Goncharov in #73)

    • +
    +
    +
    +

    0.5.2 (2017-09-10)

    +
      +
    • Added missing driver name on dialect (#67)

    • +
    • Fixed dialect to support native decimal type (#67)

    • +
    +
    +
    +

    0.5.1 (2017-09-09)

    +

    This is an emergency fix for 0.5.0.

    +
      +
    • Reverted the extension, back to pure Python (#60)

    • +
    • Used SQLAlchemy RowProxy

    • +
    • Added first_or_404

    • +
    • Fixed bug that GinoPool cannot be inherited

    • +
    +
    +
    +

    0.5.0 (2017-09-03)

    +
      +
    • [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten

      +
        +
      • Extracted CRUD operations

      • +
      • Core operations are moved to dialect and execution context

      • +
      • Removed guess_model, switched to explicit execution options

      • +
      • Turned timeout parameter to an execution option

      • +
      • Extracted pool, connection and api from asyncpg_delegate

      • +
      +
    • +
    • Added support for SQLAlchemy execution options, and a few custom options

    • +
    • [Breaking] Made Model.select return rows by default (#39)

    • +
    • Moved get_or_404 to extensions (#38)

    • +
    • Added iterator on model classes (#43)

    • +
    • Added Tornado extension (Contributed by Vladimir Goncharov)

    • +
    • Added Model.to_dict (#47)

    • +
    • Added an extension module to update asyncpg.Record with processed results

    • +
    +
    +
    +
    +

    Early Development Releases

    +

    Considered as alpha releases.

    +
    +

    0.4.1 (2017-08-20)

    +
      +
    • Support select on model instance

    • +
    +
    +
    +

    0.4.0 (2017-08-15)

    +
      +
    • Made get_or_404 more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31)

    • +
    • Delegated sqlalchemy.__all__ (Contributed by Neal Wang in #10 #33)

    • +
    • [Breaking] Rewrote JSON/JSONB support (#29)

    • +
    • Added lazy parameter on db.acquire (Contributed by Binghan Li in #32)

    • +
    • Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34)

    • +
    • Fixed iterate API to be compatible with asyncpg (#32)

    • +
    • Unified exceptions

    • +
    • [Breaking] Changed update API (#29)

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.3.0 (2017-08-07)

    +
      +
    • Supported __table_args__ (#12)

    • +
    • Introduced task local to manage connection in context (#19)

    • +
    • Added query.gino extension for in-place execution

    • +
    • Refreshed README (#3)

    • +
    • Adopted PEP 487 (Contributed by Tony Wang in #17 #27)

    • +
    • Used weakref on __model__ of table and query (Contributed by Tony Wang)

    • +
    • Delegated asyncpg timeout parameter (Contributed by Neal Wang in #16 #22)

    • +
    +
    +
    +

    0.2.3 (2017-08-04)

    +
      +
    • Supported any primary key (Contributed by Tony Wang in #11)

    • +
    +
    +
    +

    0.2.2 (2017-08-02)

    +
      +
    • Supported SQLAlchemy result processor

    • +
    • Added rich support on JSON/JSONB

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.2.1 (2017-07-28)

    +
      +
    • Added update and delete API

    • +
    +
    +
    +

    0.2.0 (2017-07-28)

    +
      +
    • Changed API, no longer reuses asyncpg API

    • +
    +
    +
    +

    0.1.1 (2017-07-25)

    +
      +
    • Added db.bind

    • +
    • API changed: parameter conn renamed to optional bind

    • +
    • Delegated asyncpg Pool with db.create_pool

    • +
    • Internal enhancement and bug fixes

    • +
    +
    +
    +

    0.1.0 (2017-07-21)

    +
      +
    • First release on PyPI.

    • +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/search.html b/docs/en/1.1b2/search.html new file mode 100644 index 0000000..2500d6c --- /dev/null +++ b/docs/en/1.1b2/search.html @@ -0,0 +1 @@ +

    search.html

    \ No newline at end of file diff --git a/docs/en/1.1b2/searchindex.js b/docs/en/1.1b2/searchindex.js new file mode 100644 index 0000000..37de280 --- /dev/null +++ b/docs/en/1.1b2/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["explanation","explanation/async","explanation/engine","explanation/sa20","explanation/why","how-to","how-to/alembic","how-to/bakery","how-to/contributing","how-to/crud","how-to/faq","how-to/json-props","how-to/loaders","how-to/pool","how-to/schema","how-to/transaction","index","reference","reference/api","reference/api/gino","reference/api/gino.aiocontextvars","reference/api/gino.api","reference/api/gino.bakery","reference/api/gino.crud","reference/api/gino.declarative","reference/api/gino.dialects","reference/api/gino.dialects.aiomysql","reference/api/gino.dialects.asyncpg","reference/api/gino.dialects.base","reference/api/gino.engine","reference/api/gino.exceptions","reference/api/gino.ext","reference/api/gino.json_support","reference/api/gino.loader","reference/api/gino.schema","reference/api/gino.strategies","reference/api/gino.transaction","reference/extensions","reference/extensions/sanic","reference/extensions/starlette","reference/extensions/tornado","reference/history","tutorials","tutorials/announcement","tutorials/fastapi","tutorials/tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["explanation.rst","explanation/async.rst","explanation/engine.rst","explanation/sa20.rst","explanation/why.rst","how-to.rst","how-to/alembic.rst","how-to/bakery.rst","how-to/contributing.rst","how-to/crud.rst","how-to/faq.rst","how-to/json-props.rst","how-to/loaders.rst","how-to/pool.rst","how-to/schema.rst","how-to/transaction.rst","index.rst","reference.rst","reference/api.rst","reference/api/gino.rst","reference/api/gino.aiocontextvars.rst","reference/api/gino.api.rst","reference/api/gino.bakery.rst","reference/api/gino.crud.rst","reference/api/gino.declarative.rst","reference/api/gino.dialects.rst","reference/api/gino.dialects.aiomysql.rst","reference/api/gino.dialects.asyncpg.rst","reference/api/gino.dialects.base.rst","reference/api/gino.engine.rst","reference/api/gino.exceptions.rst","reference/api/gino.ext.rst","reference/api/gino.json_support.rst","reference/api/gino.loader.rst","reference/api/gino.schema.rst","reference/api/gino.strategies.rst","reference/api/gino.transaction.rst","reference/extensions.rst","reference/extensions/sanic.rst","reference/extensions/starlette.rst","reference/extensions/tornado.rst","reference/history.rst","tutorials.rst","tutorials/announcement.rst","tutorials/fastapi.rst","tutorials/tutorial.rst"],objects:{"":{gino:[19,0,0,"-"]},"gino.aiocontextvars":{patch_asyncio:[20,1,1,""]},"gino.api":{Gino:[21,2,1,""],GinoExecutor:[21,2,1,""]},"gino.api.Gino":{Model:[21,3,1,""],acquire:[21,3,1,""],all:[21,3,1,""],bake:[21,3,1,""],bakery:[21,3,1,""],bind:[21,3,1,""],compile:[21,3,1,""],first:[21,3,1,""],iterate:[21,3,1,""],model_base_classes:[21,4,1,""],no_delegate:[21,4,1,""],one:[21,3,1,""],one_or_none:[21,3,1,""],pop_bind:[21,3,1,""],query_executor:[21,4,1,""],scalar:[21,3,1,""],schema_visitor:[21,4,1,""],set_bind:[21,3,1,""],status:[21,3,1,""],transaction:[21,3,1,""],with_bind:[21,3,1,""]},"gino.api.GinoExecutor":{all:[21,3,1,""],execution_options:[21,3,1,""],first:[21,3,1,""],iterate:[21,3,1,""],load:[21,3,1,""],model:[21,3,1,""],one:[21,3,1,""],one_or_none:[21,3,1,""],query:[21,3,1,""],return_model:[21,3,1,""],scalar:[21,3,1,""],status:[21,3,1,""],timeout:[21,3,1,""]},"gino.bakery":{BakedQuery:[22,2,1,""],Bakery:[22,2,1,""]},"gino.bakery.BakedQuery":{bind:[22,3,1,""],compiled_sql:[22,3,1,""],execution_options:[22,3,1,""],get:[22,3,1,""],query:[22,3,1,""],sql:[22,3,1,""]},"gino.bakery.Bakery":{bake:[22,3,1,""],query_cls:[22,4,1,""]},"gino.crud":{Alias:[23,2,1,""],CRUDModel:[23,2,1,""],QueryModel:[23,2,1,""],UpdateRequest:[23,2,1,""]},"gino.crud.Alias":{distinct:[23,3,1,""],load:[23,3,1,""],on:[23,3,1,""]},"gino.crud.CRUDModel":{"delete":[23,4,1,""],alias:[23,3,1,""],append_where_primary_key:[23,3,1,""],create:[23,3,1,""],distinct:[23,3,1,""],get:[23,3,1,""],in_query:[23,3,1,""],load:[23,3,1,""],lookup:[23,3,1,""],none_as_none:[23,3,1,""],on:[23,3,1,""],query:[23,4,1,""],select:[23,3,1,""],to_dict:[23,3,1,""],update:[23,4,1,""]},"gino.crud.UpdateRequest":{apply:[23,3,1,""],update:[23,3,1,""]},"gino.declarative":{ColumnAttribute:[24,2,1,""],InvertDict:[24,2,1,""],Model:[24,2,1,""],declarative_base:[24,1,1,""],declared_attr:[24,1,1,""]},"gino.declarative.InvertDict":{invert_get:[24,3,1,""]},"gino.dialects":{aiomysql:[26,0,0,"-"],asyncpg:[27,0,0,"-"],base:[28,0,0,"-"]},"gino.dialects.aiomysql":{AiomysqlDBAPI:[26,2,1,""],AiomysqlDialect:[26,2,1,""],AiomysqlExecutionContext:[26,2,1,""],AiomysqlIterator:[26,2,1,""],AsyncEnum:[26,2,1,""],DBAPICursor:[26,2,1,""],GinoNullType:[26,2,1,""],Pool:[26,2,1,""],Transaction:[26,2,1,""]},"gino.dialects.aiomysql.AiomysqlDBAPI":{paramstyle:[26,4,1,""]},"gino.dialects.aiomysql.AiomysqlDialect":{colspecs:[26,4,1,""],cursor_cls:[26,4,1,""],dbapi_class:[26,4,1,""],driver:[26,4,1,""],execution_ctx_cls:[26,4,1,""],get_isolation_level:[26,3,1,""],has_table:[26,3,1,""],init_kwargs:[26,4,1,""],init_pool:[26,3,1,""],on_connect:[26,3,1,""],postfetch_lastrowid:[26,4,1,""],set_isolation_level:[26,3,1,""],statement_compiler:[26,4,1,""],support_prepare:[26,4,1,""],support_returning:[26,4,1,""],supports_native_decimal:[26,4,1,""],transaction:[26,3,1,""]},"gino.dialects.aiomysql.AiomysqlExecutionContext":{get_affected_rows:[26,3,1,""],get_lastrowid:[26,3,1,""]},"gino.dialects.aiomysql.AiomysqlIterator":{forward:[26,3,1,""],many:[26,3,1,""],next:[26,3,1,""]},"gino.dialects.aiomysql.AsyncEnum":{create_async:[26,3,1,""],drop_async:[26,3,1,""]},"gino.dialects.aiomysql.DBAPICursor":{async_execute:[26,3,1,""],description:[26,3,1,""],execute_baked:[26,3,1,""],get_statusmsg:[26,3,1,""],iterate:[26,3,1,""],prepare:[26,3,1,""]},"gino.dialects.aiomysql.GinoNullType":{result_processor:[26,3,1,""]},"gino.dialects.aiomysql.Pool":{acquire:[26,3,1,""],close:[26,3,1,""],raw_pool:[26,3,1,""],release:[26,3,1,""],repr:[26,3,1,""]},"gino.dialects.aiomysql.Transaction":{begin:[26,3,1,""],commit:[26,3,1,""],raw_transaction:[26,3,1,""],rollback:[26,3,1,""]},"gino.dialects.asyncpg":{AsyncEnum:[27,2,1,""],AsyncpgCompiler:[27,2,1,""],AsyncpgCursor:[27,2,1,""],AsyncpgDBAPI:[27,2,1,""],AsyncpgDialect:[27,2,1,""],AsyncpgExecutionContext:[27,2,1,""],AsyncpgIterator:[27,2,1,""],AsyncpgJSONPathType:[27,2,1,""],DBAPICursor:[27,2,1,""],GinoNullType:[27,2,1,""],NullPool:[27,2,1,""],Pool:[27,2,1,""],PreparedStatement:[27,2,1,""],Transaction:[27,2,1,""]},"gino.dialects.asyncpg.AsyncEnum":{create_async:[27,3,1,""],drop_async:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgCompiler":{bindtemplate:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgCursor":{forward:[27,3,1,""],many:[27,3,1,""],next:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgDBAPI":{Error:[27,4,1,""]},"gino.dialects.asyncpg.AsyncpgDialect":{colspecs:[27,4,1,""],cursor_cls:[27,4,1,""],dbapi_class:[27,4,1,""],driver:[27,4,1,""],execution_ctx_cls:[27,4,1,""],get_isolation_level:[27,3,1,""],has_schema:[27,3,1,""],has_sequence:[27,3,1,""],has_table:[27,3,1,""],has_type:[27,3,1,""],init_kwargs:[27,4,1,""],init_pool:[27,3,1,""],on_connect:[27,3,1,""],set_isolation_level:[27,3,1,""],statement_compiler:[27,4,1,""],supports_native_decimal:[27,4,1,""],transaction:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgJSONPathType":{bind_processor:[27,3,1,""]},"gino.dialects.asyncpg.DBAPICursor":{async_execute:[27,3,1,""],description:[27,3,1,""],execute_baked:[27,3,1,""],get_statusmsg:[27,3,1,""],prepare:[27,3,1,""]},"gino.dialects.asyncpg.GinoNullType":{result_processor:[27,3,1,""]},"gino.dialects.asyncpg.NullPool":{acquire:[27,3,1,""],close:[27,3,1,""],raw_pool:[27,3,1,""],release:[27,3,1,""],repr:[27,3,1,""]},"gino.dialects.asyncpg.Pool":{acquire:[27,3,1,""],close:[27,3,1,""],raw_pool:[27,3,1,""],release:[27,3,1,""],repr:[27,3,1,""]},"gino.dialects.asyncpg.Transaction":{begin:[27,3,1,""],commit:[27,3,1,""],raw_transaction:[27,3,1,""],rollback:[27,3,1,""]},"gino.dialects.base":{AsyncDialectMixin:[28,2,1,""],BaseDBAPI:[28,2,1,""],Cursor:[28,2,1,""],DBAPICursor:[28,2,1,""],ExecutionContextOverride:[28,2,1,""],Pool:[28,2,1,""],PreparedStatement:[28,2,1,""],Transaction:[28,2,1,""]},"gino.dialects.base.AsyncDialectMixin":{compile:[28,3,1,""],cursor_cls:[28,4,1,""],dbapi:[28,3,1,""],dbapi_class:[28,4,1,""],init_pool:[28,3,1,""],support_prepare:[28,4,1,""],support_returning:[28,4,1,""],transaction:[28,3,1,""]},"gino.dialects.base.BaseDBAPI":{Binary:[28,3,1,""],Error:[28,4,1,""],paramstyle:[28,4,1,""]},"gino.dialects.base.Cursor":{forward:[28,3,1,""],many:[28,3,1,""],next:[28,3,1,""]},"gino.dialects.base.DBAPICursor":{async_execute:[28,3,1,""],description:[28,3,1,""],execute:[28,3,1,""],execute_baked:[28,3,1,""],executemany:[28,3,1,""],get_statusmsg:[28,3,1,""],prepare:[28,3,1,""]},"gino.dialects.base.ExecutionContextOverride":{baked_query:[28,4,1,""],get_affected_rows:[28,3,1,""],get_lastrowid:[28,3,1,""],get_result_proxy:[28,3,1,""],loader:[28,4,1,""],model:[28,4,1,""],process_rows:[28,3,1,""],return_model:[28,4,1,""],timeout:[28,4,1,""]},"gino.dialects.base.Pool":{acquire:[28,3,1,""],close:[28,3,1,""],raw_pool:[28,3,1,""],release:[28,3,1,""],repr:[28,3,1,""]},"gino.dialects.base.PreparedStatement":{all:[28,3,1,""],first:[28,3,1,""],iterate:[28,3,1,""],scalar:[28,3,1,""],status:[28,3,1,""]},"gino.dialects.base.Transaction":{begin:[28,3,1,""],commit:[28,3,1,""],raw_transaction:[28,3,1,""],rollback:[28,3,1,""]},"gino.engine":{GinoConnection:[29,2,1,""],GinoEngine:[29,2,1,""]},"gino.engine.GinoConnection":{all:[29,3,1,""],dialect:[29,3,1,""],execution_options:[29,3,1,""],first:[29,3,1,""],get_raw_connection:[29,3,1,""],iterate:[29,3,1,""],one:[29,3,1,""],one_or_none:[29,3,1,""],prepare:[29,3,1,""],raw_connection:[29,3,1,""],release:[29,3,1,""],scalar:[29,3,1,""],schema_for_object:[29,4,1,""],status:[29,3,1,""],transaction:[29,3,1,""]},"gino.engine.GinoEngine":{acquire:[29,3,1,""],all:[29,3,1,""],close:[29,3,1,""],compile:[29,3,1,""],connection_cls:[29,4,1,""],current_connection:[29,3,1,""],dialect:[29,3,1,""],first:[29,3,1,""],iterate:[29,3,1,""],one:[29,3,1,""],one_or_none:[29,3,1,""],raw_pool:[29,3,1,""],repr:[29,3,1,""],scalar:[29,3,1,""],status:[29,3,1,""],transaction:[29,3,1,""],update_execution_options:[29,3,1,""]},"gino.exceptions":{GinoException:[30,5,1,""],InitializedError:[30,5,1,""],MultipleResultsFound:[30,5,1,""],NoResultFound:[30,5,1,""],NoSuchRowError:[30,5,1,""],UninitializedError:[30,5,1,""],UnknownJSONPropertyError:[30,5,1,""]},"gino.json_support":{ArrayProperty:[32,2,1,""],BooleanProperty:[32,2,1,""],DateTimeProperty:[32,2,1,""],IntegerProperty:[32,2,1,""],JSONProperty:[32,2,1,""],ObjectProperty:[32,2,1,""],StringProperty:[32,2,1,""]},"gino.json_support.ArrayProperty":{decode:[32,3,1,""],encode:[32,3,1,""]},"gino.json_support.BooleanProperty":{decode:[32,3,1,""],encode:[32,3,1,""],make_expression:[32,3,1,""]},"gino.json_support.DateTimeProperty":{decode:[32,3,1,""],encode:[32,3,1,""],make_expression:[32,3,1,""]},"gino.json_support.IntegerProperty":{decode:[32,3,1,""],encode:[32,3,1,""],make_expression:[32,3,1,""]},"gino.json_support.JSONProperty":{decode:[32,3,1,""],encode:[32,3,1,""],get_profile:[32,3,1,""],make_expression:[32,3,1,""],reload:[32,3,1,""],save:[32,3,1,""]},"gino.json_support.ObjectProperty":{decode:[32,3,1,""],encode:[32,3,1,""]},"gino.json_support.StringProperty":{make_expression:[32,3,1,""]},"gino.loader":{AliasLoader:[33,2,1,""],CallableLoader:[33,2,1,""],ColumnLoader:[33,2,1,""],Loader:[33,2,1,""],ModelLoader:[33,2,1,""],TupleLoader:[33,2,1,""],ValueLoader:[33,2,1,""]},"gino.loader.CallableLoader":{do_load:[33,3,1,""]},"gino.loader.ColumnLoader":{do_load:[33,3,1,""]},"gino.loader.Loader":{do_load:[33,3,1,""],get:[33,3,1,""],get_columns:[33,3,1,""],get_from:[33,3,1,""],query:[33,3,1,""]},"gino.loader.ModelLoader":{distinct:[33,3,1,""],do_load:[33,3,1,""],get_columns:[33,3,1,""],get_from:[33,3,1,""],load:[33,3,1,""],none_as_none:[33,3,1,""],on:[33,3,1,""]},"gino.loader.TupleLoader":{do_load:[33,3,1,""]},"gino.loader.ValueLoader":{do_load:[33,3,1,""]},"gino.schema":{AsyncSchemaDropper:[34,2,1,""],AsyncSchemaGenerator:[34,2,1,""],AsyncSchemaTypeMixin:[34,2,1,""],AsyncVisitor:[34,2,1,""],GinoSchemaVisitor:[34,2,1,""],patch_schema:[34,1,1,""]},"gino.schema.AsyncSchemaDropper":{visit_foreign_key_constraint:[34,3,1,""],visit_index:[34,3,1,""],visit_metadata:[34,3,1,""],visit_sequence:[34,3,1,""],visit_table:[34,3,1,""]},"gino.schema.AsyncSchemaGenerator":{visit_foreign_key_constraint:[34,3,1,""],visit_index:[34,3,1,""],visit_metadata:[34,3,1,""],visit_sequence:[34,3,1,""],visit_table:[34,3,1,""]},"gino.schema.AsyncSchemaTypeMixin":{create_async:[34,3,1,""],drop_async:[34,3,1,""]},"gino.schema.AsyncVisitor":{traverse_single:[34,3,1,""]},"gino.schema.GinoSchemaVisitor":{create:[34,3,1,""],create_all:[34,3,1,""],drop:[34,3,1,""],drop_all:[34,3,1,""]},"gino.strategies":{GinoStrategy:[35,2,1,""]},"gino.strategies.GinoStrategy":{create:[35,3,1,""],engine_cls:[35,4,1,""],name:[35,4,1,""]},"gino.transaction":{GinoTransaction:[36,2,1,""]},"gino.transaction.GinoTransaction":{commit:[36,3,1,""],connection:[36,3,1,""],raise_commit:[36,3,1,""],raise_rollback:[36,3,1,""],raw_transaction:[36,3,1,""],rollback:[36,3,1,""]},gino:{aiocontextvars:[20,0,0,"-"],api:[21,0,0,"-"],bakery:[22,0,0,"-"],create_engine:[19,1,1,""],crud:[23,0,0,"-"],declarative:[24,0,0,"-"],dialects:[25,0,0,"-"],engine:[29,0,0,"-"],exceptions:[30,0,0,"-"],ext:[31,0,0,"-"],get_version:[19,1,1,""],json_support:[32,0,0,"-"],loader:[33,0,0,"-"],schema:[34,0,0,"-"],strategies:[35,0,0,"-"],transaction:[36,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","attribute","Python attribute"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:attribute","5":"py:exception"},terms:{"0020":43,"0x10a8ba860":14,"100":[3,4,23,44],"101":[0,4],"106":41,"109":41,"112":41,"113":[10,41],"114":41,"11th":4,"123":7,"126":41,"127":44,"128":36,"141":41,"142":41,"146":41,"147":41,"159":41,"160":41,"174":41,"178":41,"180":41,"183":41,"184":41,"187":41,"191":41,"192":41,"193":41,"198":41,"1990":11,"2017":[17,43],"2018":[12,17],"2019":17,"202":41,"2020":[16,17],"204":41,"213":41,"215":41,"216":41,"21s":44,"224":41,"225":41,"228":41,"231":41,"249":3,"258":41,"261":41,"265":41,"275":41,"279":41,"280":41,"281":41,"282":41,"287":41,"288":41,"289":41,"291":41,"297":41,"298":41,"302":41,"304":41,"305":41,"307":41,"308":41,"309":41,"310":41,"313":41,"323":41,"32c0feba61ea":44,"32c0feba61ea_add_users_t":44,"333":41,"334":41,"335":41,"351":41,"365":41,"378":41,"382":41,"387":41,"393":41,"395":41,"396":41,"3apull_request":8,"400":38,"401":41,"402":41,"403":41,"404":44,"406":41,"407":41,"408":41,"411":41,"425":41,"427":41,"431847":12,"433":41,"437":41,"440":41,"441":41,"447":41,"451":41,"457":41,"478":41,"486":41,"487":41,"4888":43,"4c59ad":41,"504":41,"518":41,"520":41,"53010":44,"53015":44,"533":41,"538":41,"5432":[8,38,39,44],"5433":8,"567":41,"569":41,"573":41,"577":41,"579":41,"582":41,"585":41,"592":41,"599":41,"600":[8,41],"609":41,"627":43,"628":41,"629":41,"63562":44,"63563":44,"637":41,"638":41,"655":41,"659":41,"660":41,"661":41,"662":41,"667":41,"672":41,"673":41,"674":41,"693":41,"694":41,"695":41,"696":41,"8000":44,"\u00e1d\u00e1m":[10,41],"\u4e00\u4e2a\u6c47\u7387":43,"\u4e00\u5b9a\u8981\u5168":43,"\u4e00\u65e6\u4ee3\u7801\u521d\u5177\u89c4\u6a21":43,"\u4e00\u679a":43,"\u4e00\u6837":43,"\u4e00\u79d2\u53ef\u8bfb\u767e\u4e07\u884c\u7684":43,"\u4e00\u7ad9\u5f0f\u5730\u89e3\u51b3\u4e86\u5e38\u7528":43,"\u4e09\u5e74\u540e\u7684\u4eca\u5929":43,"\u4e0a\u4e0b\u6587\u7ba1\u7406":43,"\u4e0a\u624b\u540e\u4f9d\u7136\u53ef\u4ee5\u5feb\u901f":43,"\u4e0a\u7ed9":43,"\u4e0a\u9762\u90a3\u4e2a":43,"\u4e0d\u4f1a\u53bb\u65e0\u7aef\u731c\u6d4b\u4e3b\u4eba\u7684\u610f\u56fe":43,"\u4e0d\u540c\u7684\u662f":43,"\u4e0d\u65ad\u6f14\u8fdb\u6210\u719f":43,"\u4e0d\u662f":43,"\u4e0d\u662f\u4e00\u4e2a\u4f20\u7edf\u7684":43,"\u4e0d\u6b62\u8fd9\u6837":43,"\u4e0e":43,"\u4e13\u4e1a\u7248\u5168\u5bb6\u6876":43,"\u4e13\u4e1a\u9886\u57df\u7684":43,"\u4e1a\u4f59\u65f6\u95f4\u9664\u4e86\u5f00\u53d1\u7ef4\u62a4":43,"\u4e2d\u8d1f\u8d23\u6784\u5efa":43,"\u4e3a":43,"\u4e3a\u4e86\u8ba9\u4e0d\u540c\u5e94\u7528\u573a\u666f\u4e0b\u7684\u7528\u6237\u4f53\u9a8c\u5230\u6700\u5927\u7684\u5584\u610f":43,"\u4e3a\u4e86\u9e21\u6bdb\u849c\u76ae\u7684\u5c0f\u4e8b\u5927\u52a8\u5e72\u6208":43,"\u4e3a\u4ec0\u4e48\u8fd9\u4e48\u773c\u719f":43,"\u4e3b\u8981\u8bf4\u7684\u662f\u5f00\u53d1\u6548\u7387":43,"\u4e4b\u7985\u5b8c\u7f8e\u8868\u8fbe\u4e86":43,"\u4e5f\u53ef\u4ee5\u7528":43,"\u4e5f\u63d0\u4f9b\u4e86\u5bf9\u8c61\u6620\u5c04\u7684\u5de5\u5177":43,"\u4e5f\u662f":43,"\u4e5f\u66fe\u5728\u521b\u4e1a\u7684\u6f6e\u6d41\u4e2d\u7559\u4e0b\u8eab\u5f71":43,"\u4e5f\u6709\u4e00\u5957\u7cbe\u5999\u7684\u663e\u5f0f\u673a\u5236":43,"\u4e86\u89e3\u4e00\u4e0b":43,"\u4e8c\u5341\u4e94\u516d\u5e74\u7684\u7f16\u7a0b\u53f2\u548c\u5341\u4e09\u56db\u5e74\u7684\u5de5\u4f5c\u7ecf\u9a8c\u6559\u4f1a\u4e86\u6211\u8bb8\u591a\u8f6f\u4ef6\u5f00\u53d1\u7684\u5965\u4e49":43,"\u4e92\u91d1":43,"\u4eb2\u8eab\u7ecf\u5386\u5e76\u89c1\u8bc1\u4e86\u8f6f\u4ef6\u6280\u672f\u968f\u7740\u624b\u6e38":43,"\u4ec0\u4e48\u7684\u90fd\u6709":43,"\u4ece\u4e0a\u624b\u6559\u7a0b\u5230\u539f\u7406\u8bf4\u660e\u5e94\u6709\u5c3d\u6709":43,"\u4ece\u6bd4\u8f83\u65e9\u5c31\u89e3\u8026\u4e86\u4e0d\u540c":43,"\u4ece\u7b80\u5355\u793a\u8303\u5230\u751f\u4ea7\u73af\u5883\u7684\u5404\u79cd\u4f8b\u5b50\u54c1\u79cd\u9f50\u5168":43,"\u4ee5\u4e0b\u662f\u8fd1\u6765\u7edf\u8ba1\u5230\u7684\u5173\u4e8e":43,"\u4ee5\u53ca":43,"\u4ee5\u53ca\u4e0b\u9762\u8fd9\u4e9b\u4e00\u76f4\u9700\u8981\u7684\u5e2e\u52a9":43,"\u4ee5\u53ca\u4e2d\u6587":43,"\u4ee5\u540c\u65f6\u83b7\u53d6\u6240\u6709\u7684\u4e66\u548c\u4ed6\u4eec\u7684\u4f5c\u8005":43,"\u4ee5\u8282\u7701\u5b66\u4e60\u548c\u8fc1\u79fb\u6210\u672c":43,"\u4f18\u52bf\u4e0e\u4e0d\u8db3":42,"\u4f20\u7edf":43,"\u4f46":43,"\u4f46\u4e0d\u662f\u4e00\u4e2a\u4f20\u7edf\u7684":43,"\u4f46\u5e94\u7528\u5230\u5927\u578b\u9879\u76ee\u4e2d\u5374\u5341\u5206\u8003\u9a8c\u5f00\u53d1\u4eba\u5458\u7684\u5e73\u5747\u6c34\u5e73":43,"\u4f46\u662f\u5bf9\u4e8e\u66f4\u590d\u6742\u7684\u67e5\u8be2":43,"\u4f46\u6ca1\u5f81\u5f97\u540c\u610f\u5c31\u4e0d\u8d34\u51fa\u6765\u4e86":43,"\u4f46\u90fd\u662f\u540c\u884c\u5c31\u4e0d\u591a\u8bc4\u4ef7\u4e86":43,"\u4f60\u4e00\u5b9a\u4f1a\u6709\u611f\u77e5\u7684":43,"\u4f60\u53ef\u4ee5\u7528":43,"\u4f60\u751a\u81f3\u53ef\u4ee5\u624b\u5199\u4efb\u4f55":43,"\u4f60\u80af\u5b9a\u8981\u95ee\u4e00\u53e5":43,"\u4f8b\u5982\u524d\u9762\u7684":43,"\u4fc4\u6587\u7684\u7ffb\u8bd1":43,"\u4fc4\u8bed\u6559\u7a0b":43,"\u4fee":43,"\u501f\u9274\u4e86":43,"\u505a\u529f\u80fd":43,"\u5143":43,"\u5148\u8bf4":42,"\u5168\u90fd\u517c\u5bb9":43,"\u5173\u4e8e\u4f5c\u8005":42,"\u5176\u4e2d":43,"\u518d\u52a0\u4e0a":43,"\u518d\u8bf4":42,"\u5199":43,"\u51fa\u54c1\u5fc5\u5c5e\u7cbe\u54c1\u7684":43,"\u51fa\u54c1\u65b9\u662f":43,"\u51fa\u95ee\u9898\u627e\u4e0d\u5230\u539f\u56e0":43,"\u5219\u4f1a\u5c06\u8fd9\u4e9b\u53d8\u66f4\u5e94\u7528\u5230\u6570\u636e\u5e93\u91cc":43,"\u5219\u662f":43,"\u521b\u9020\u51fa\u4e86\u4e00\u79cd\u7206\u70b8\u5f0f\u7684\u5316\u5b66\u53cd\u5e94":43,"\u52a0\u4e00\u9897\u661f\u661f":43,"\u52a0\u8f7d\u5668\u4e0e\u5173\u7cfb":43,"\u52a0\u8f7d\u5668\u7684\u7528\u6cd5\u4e5f\u662f\u5f88\u7b80\u5355\u7684":43,"\u5341\u5206\u5feb\u6377":43,"\u5343\u661f\u9879\u76ee":43,"\u5373\u5173\u7cfb\u5bf9\u8c61\u6620\u5c04":43,"\u5373\u88c5\u5373\u7528":43,"\u539f\u6559\u65e8\u4e3b\u4e49\u8005":43,"\u53bb":43,"\u53c2\u4e0e\u8ba8\u8bba":43,"\u53c2\u8003\u6587\u732e":42,"\u53c8\u7b80\u5355\u660e\u4e86\u5730\u8bbf\u95ee\u6570\u636e\u5e93\u5462":43,"\u53cd\u566c\u7684\u60c5\u666f":43,"\u53e6\u4e00\u65b9\u9762":43,"\u53e6\u5916":43,"\u53ea\u613f\u5b9a\u4e49":43,"\u53ea\u6709\u5f02\u6b65\u6267\u884c\u65f6\u624d\u7528\u5230":43,"\u53ef\u4ee5\u76f4\u63a5\u83b7\u53d6\u5230":43,"\u53ef\u4ee5\u8c28\u614e\u5730\u7528\u4e8e\u751f\u4ea7\u73af\u5883\u4e86":43,"\u53ef\u5b9a\u5236\u5316\u7684\u52a0\u8f7d\u5668":43,"\u53ef\u9009":43,"\u5404\u4e2a":43,"\u5404\u5927\u6d41\u884c\u5f02\u6b65":43,"\u5404\u79cd":43,"\u540c\u65f6":43,"\u540c\u65f6\u4e5f\u4e0d\u9700\u8981\u4e3a\u8fd9\u79cd\u660e\u786e\u6027\u4ed8\u51fa\u8fc7\u5927\u7684\u5de5\u7a0b\u4ee3\u4ef7":43,"\u5462":43,"\u548c":43,"\u54a6":43,"\u54e6\u4e0d":43,"\u56de\u7b54\u95ee\u9898":43,"\u56e0\u4e3a\u7269\u6781\u5fc5\u53cd":43,"\u56e0\u6b64":43,"\u56e0\u6b64\u8fd8\u4e0d\u80fd\u5b8c\u5168\u53d1\u6325":43,"\u5728\u5e26\u6765\u751f\u6d3b\u4fbf\u5229\u7684\u540c\u65f6":43,"\u5728\u6267\u884c\u6548\u7387\u4e0a\u4e5f\u6ca1\u843d\u4e0b":43,"\u5728\u7ebf\u6e38\u620f\u7b49\u9ad8\u5e76\u53d1\u9886\u57df":43,"\u5728\u8fd9\u4e2a\u70e7\u8111\u7684\u5f02\u6b65\u4e16\u754c\u91cc":43,"\u5728\u8fd9\u91cc\u5c31\u4e0d\u4e00\u4e00\u5217\u4e3e\u4e86":43,"\u57fa\u4e8e":43,"\u57fa\u7840\u6559\u7a0b":43,"\u586b\u8865\u4e86\u56fd\u5185\u5916":43,"\u589e\u5220\u6539\u67e5":43,"\u5927":43,"\u5927\u5b66\u91cc\u5f00\u59cb\u5199":43,"\u5927\u91cf\u7684\u6210\u529f\u6848\u4f8b\u4e5f\u8bc1\u660e\u4e86":43,"\u5929\u751f\u538c\u6076":43,"\u5982\u679c\u50cf\u8fd9\u6837":43,"\u5988\u5988\u518d\u4e5f\u4e0d\u7528\u62c5\u5fc3\u6211\u4e0d\u4f1a\u96c6\u6210":43,"\u5b83\u4eec":43,"\u5b83\u4eec\u5173\u6ce8\u7684\u91cd\u70b9\u4e0e":43,"\u5b83\u7684\u5168\u79f0\u662f":43,"\u5b83\u7684\u5b9e\u4f8b":43,"\u5b98\u5ba3":42,"\u5b9a\u4e0b\u4e86\u4e24\u4e2a\u4e1a\u7ee9\u76ee\u6807":43,"\u5b9e\u4f8b":43,"\u5b9e\u4f8b\u7684":43,"\u5b9e\u4f8b\u8bbe\u7f6e\u5230":43,"\u5bf9":43,"\u5bf9\u4e8e\u4e0a\u4e86\u89c4\u6a21\u7684\u5f02\u6b65\u5de5\u7a0b\u9879\u76ee\u6765\u8bf4\u5c24\u4e3a\u91cd\u8981":43,"\u5bf9\u4e8e\u5982\u4f55\u5c06\u6570\u636e\u5e93\u67e5\u8be2\u7ed3\u679c\u7ec4\u88c5\u6210\u5185\u5b58\u5bf9\u8c61\u53ca\u5176\u5c5e\u6027":43,"\u5bf9\u4e8e\u7b80\u5355\u76f4\u89c2\u7684\u4e00\u5bf9\u4e00\u52a0\u8f7d":43,"\u5bf9\u5176\u6267\u884c":43,"\u5bf9\u8c61":43,"\u5bf9\u8c61\u5173\u7cfb\u6620\u5c04":43,"\u5c06\u6570\u636e\u5e93\u8fd4\u56de\u7ed3\u679c\u7684\u6bcf\u4e00\u884c\u4e2d":43,"\u5c0f\u65f6\u5019\u5199":43,"\u5c31\u53d8\u6210\u4e86\u4eba\u540d":43,"\u5c31\u5b9e\u73b0\u4e86":43,"\u5c31\u662f\u4ed6\u4eec\u7684\u4f5c\u54c1":43,"\u5c31\u662f\u5185\u5b58\u91cc\u9762\u7684\u5e38\u89c4\u5bf9\u8c61":43,"\u5c31\u662f\u660e\u786e\u6027\u7684\u5173\u952e":43,"\u5c31\u662f\u6709\u4eba\u53d7\u4e0d\u4e86\u4e86\u81ea\u5df1\u5199\u4e86\u4e00\u4e2a\u7c7b\u578b\u6ce8\u89e3":43,"\u5c31\u6ca1\u6709\u6570\u636e\u5e93\u64cd\u4f5c":43,"\u5c5e\u4e8e":43,"\u5c5e\u6027\u4e0a":43,"\u5c81\u5f00\u59cb\u63a5\u89e6\u7f16\u7a0b":43,"\u5de5\u4f5c\u5934\u4e94\u5e74\u8f6c\u5411\u4e86":43,"\u5df2\u7ecf\u521d\u6b65\u5177\u5907\u53d1\u5e03":43,"\u5e74\u521b\u4f5c\u4e4b\u521d":43,"\u5e74\u751f\u4eba":43,"\u5e76\u4e0d\u662f\u4ece\u5934\u9020\u8f6e\u5b50":43,"\u5e76\u63a5\u89e6\u5230\u4e86":43,"\u5efa\u8bbe\u793e\u4f1a\u4e3b\u4e49":42,"\u5f00\u53d1\u4eba\u5458\u7684\u65f6\u95f4\u786e\u5b9e\u6bd4\u673a\u5668\u7684\u65f6\u95f4\u503c\u94b1":43,"\u5f00\u53d1\u5e94\u7528\u7a0b\u5e8f\u7684\u65f6\u5019\u4e0d\u7528\u62c5\u5fc3\u4f1a\u88ab\u610f\u6599\u4e4b\u5916\u7684\u884c\u4e3a\u6240\u60ca\u5413\u5230":43,"\u5f00\u6e90\u793e\u533a\u91cc\u4e5f\u76f8\u7ee7\u51fa\u73b0\u4e86\u50cf":43,"\u5f00\u7bb1\u5373\u7528\u7684\u6570\u636e\u5e93\u53d8\u66f4\u7ba1\u7406\u5de5\u5177":43,"\u5f02\u6b65":43,"\u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668":42,"\u5f02\u6b65\u7f16\u7a0b\u8fd9\u4e2a\u8bdd\u9898\u4e5f\u5728\u9010\u6e10\u5347\u6e29":43,"\u5f15\u64ce":43,"\u5f15\u64ce\u4e0e\u8fde\u63a5":43,"\u5f62\u4f3c\u800c\u795e\u4e0d\u4f3c":43,"\u5f80\u5f80\u4f1a\u9009\u62e9\u727a\u7272\u660e\u786e\u6027":43,"\u5f88\u7b80\u5355\u7684\u4e00\u4e2a\u5916\u8fde\u63a5\u67e5\u8be2":43,"\u5f97\u5929\u72ec\u539a\u7684\u7075\u6d3b\u6027":43,"\u5feb\u4e50\u5730\u7f16\u7a0b":43,"\u5feb\u6377\u65b9\u5f0f":43,"\u6027\u80fd\u83ab\u540d\u5176\u5999\u7684\u5dee":43,"\u610f\u5473\u7740\u6211\u4eec\u8981\u8df3\u51fa\u53bb\u6267\u884c\u6570\u636e\u5e93\u64cd\u4f5c\u4e86":43,"\u6210\u529f\u5b9e\u73b0\u4e86\u5bf9\u540c\u671f\u7ade\u54c1":43,"\u6211\u5c31\u7ed9":43,"\u6211\u771f":43,"\u6211\u7d22\u6027\u5728":43,"\u6216\u8005\u7528":43,"\u6240\u4ee5":43,"\u6240\u4ee5\u5728":43,"\u6240\u4ee5\u6b22\u8fce\u5927\u5bb6\u4e00\u8d77\u6765\u5efa\u8bbe":43,"\u6240\u4ee5\u8fd9\u4e9b\u529f\u80fd\u4f1a\u9646\u7eed\u5728":43,"\u6240\u6709\u4eba":43,"\u6240\u6709\u8fd9\u4e9b\u95ee\u9898\u5982\u679c\u518d\u653e\u8fdb\u5f02\u6b65\u7f16\u7a0b\u7684\u73af\u5883\u91cc":43,"\u624b\u6cd5":43,"\u6267\u884c":43,"\u627e":43,"\u6307\u54ea\u513f\u6253\u54ea\u513f":43,"\u6362\u53e5\u8bdd\u8bf4":43,"\u63d0":43,"\u63d0\u5347\u4e86\u5199\u4ee3\u7801\u7684\u5e78\u798f\u6307\u6570":43,"\u63d0\u5efa\u8bae":43,"\u6570\u636e\u5e93\u4e8b\u52a1":43,"\u6570\u636e\u5e93\u4e8b\u52a1\u5c01\u88c5\u548c\u5d4c\u5957":43,"\u6587\u5a31":43,"\u65b0\u5a92\u4f53":43,"\u65b9\u4fbf\u5feb\u6377":42,"\u65b9\u8a00\u548c\u9a71\u52a8\u7684\u96c6\u6210":43,"\u65e0\u989d\u5916\u4f9d\u8d56\u5173\u7cfb":43,"\u65e2\u7b80\u5355\u53c8\u660e\u4e86\u6709\u6ca1\u6709":43,"\u660e\u786e\u6027":43,"\u662f":43,"\u662f\u4e00\u4e2a":43,"\u662f\u4e00\u4e2a\u5f00\u6e90\u9879\u76ee":43,"\u662f\u4e00\u7c7b\u5f00\u53d1\u4eba\u5458\u559c\u95fb\u4e50\u89c1\u7684\u6548\u7387\u5de5\u5177":43,"\u662f\u4e0d\u662f\u5341\u5206\u65b9\u4fbf":43,"\u662f\u5b8c\u5168\u65e0\u72b6\u6001\u7684\u666e\u901a":43,"\u662f\u7528\u6765\u8bbf\u95ee\u6570\u636e\u5e93\u7684":43,"\u662f\u7684":43,"\u662f\u8c01":42,"\u66f4\u591a\u7684\u4f8b\u5b50\u548c\u6587\u6863":43,"\u66f4\u91cd\u8981\u7684\u662f\u5e26\u6765\u4e86\u6574\u4e2a":43,"\u6700\u540e":43,"\u6700\u540e\u4e5f\u662f\u6700\u91cd\u8981\u7684":43,"\u6700\u540e\u5c06":43,"\u6700\u5927\u7a0b\u5ea6\u7684\u4fbf\u5229":43,"\u6700\u5c11\u4fb5\u5165\u578b":43,"\u6709\u6ca1\u6709\u529e\u6cd5\u53ef\u4ee5\u65e2\u65b9\u4fbf\u5feb\u6377":43,"\u670d\u52a1":43,"\u671f\u95f4\u8d21\u732e\u4e86":43,"\u6765\u4fee\u6539\u5c5e\u6027":43,"\u6765\u521b\u5efa\u65b0\u7684\u5b9e\u4f8b":43,"\u6765\u6362\u53d6\u4fbf\u6377\u6027":43,"\u6781\u5927\u5730":43,"\u67d0\u4e9b\u573a\u666f\u4e0b":43,"\u6846\u67b6":43,"\u6846\u67b6\u4e86":43,"\u6846\u67b6\u63d2\u4ef6\u7684\u7ef4\u62a4\u5de5\u4f5c\u9700\u8981\u591a\u4eba\u8ba4\u9886":43,"\u6846\u67b6\u6765\u8bf4\u662f\u4e0d\u53ef\u63a5\u53d7\u7684":43,"\u6846\u67b6\u7684\u5b9a\u5236\u7248\u63d2\u4ef6":43,"\u6b22\u8fce\u6765\u5230":43,"\u6b63\u72b9\u5982":43,"\u6bd4\u5982\u4e00\u4e2a\u7528\u6237\u53ef\u80fd\u5199\u4e86\u5f88\u591a\u672c\u4e66":43,"\u6bd4\u5982\u6267\u884c":43,"\u6bd4\u5982\u6ca1\u6709\u7167\u987e\u5230":43,"\u6bd4\u5982\u7528":43,"\u6bd4\u5982\u8bf4":43,"\u6c7d\u8f66\u7b49\u884c\u4e1a\u7684\u8d77\u8d77\u4f0f\u4f0f":43,"\u6ca1\u6709":43,"\u6ca1\u6709\u53d1\u7968":43,"\u6ca1\u6709\u9519":43,"\u6df1\u53d7\u4fc4\u7f57\u65af\u548c\u4e4c\u514b\u5170\u4eba\u6c11\u7684\u7231\u6234":43,"\u706b\u529b\u5168\u5f00\u578b":43,"\u7136\u540e\u5b9a\u5236\u52a0\u8f7d\u5668\u81ea\u52a8\u52a0\u8f7d\u6210\u671f\u671b\u7684\u5bf9\u8c61\u5173\u7cfb":43,"\u7136\u540e\u5c06\u8be5\u884c\u4e2d\u5269\u4e0b\u7684\u5c5e\u4e8e":43,"\u7136\u540e\u8fd9\u6837\u6765\u52a0\u8f7d\u8fd9\u79cd\u591a\u5bf9\u4e00\u5173\u7cfb":43,"\u7248\u672c\u4e2d\u8ddf\u4e0a":43,"\u7279\u522b\u611f\u8c22":43,"\u751f\u957f\u7684\u6e29\u5e8a":43,"\u7528":43,"\u7528\u6237\u5305":43,"\u7535\u5546":43,"\u7684":43,"\u7684\u540c\u5b66\u6765\u8bf4\u53ef\u80fd\u5e76\u4e0d\u964c\u751f":43,"\u7684\u57fa\u7840\u4e0a\u5f00\u53d1\u7684":43,"\u7684\u589e\u5f3a\u63d2\u4ef6":43,"\u7684\u590d\u6742\u5ea6\u4e86":43,"\u7684\u5b57\u6bb5\u52a0\u8f7d\u6210\u4e00\u4e2a":43,"\u7684\u5b66\u4e60\u66f2\u7ebf\u524d\u5e73\u540e\u9661":43,"\u7684\u5b9a\u4e49\u98ce\u683c":43,"\u7684\u5e94\u7528\u6848\u4f8b":43,"\u7684\u5e95\u5c42\u6838\u5fc3":43,"\u7684\u5f3a\u529b\u52a0\u6301":43,"\u7684\u5f88\u591a\u8bbe\u8ba1\u90fd\u53d7\u5230\u4e86\u660e\u786e\u6027\u7684\u5f71\u54cd":43,"\u7684\u652f\u6301":43,"\u7684\u6587\u6863":43,"\u7684\u6700\u5927\u4f18\u52bf\u8fd8\u662f\u5728\u4e8e\u5145\u5206\u5e73\u8861\u4e86\u5f00\u53d1\u6548\u7387\u548c\u660e\u786e\u6027\u4e4b\u95f4\u7684\u8fa9\u8bc1\u77db\u76fe\u5173\u7cfb":43,"\u7684\u6f5c\u80fd":43,"\u7684\u751f\u6001\u73af\u5883":43,"\u7684\u7528\u6237\u5bf9\u8c61":43,"\u7684\u7acb\u573a":43,"\u7684\u7c7b\u578b\u63d0\u793a":43,"\u7684\u8d21\u732e":43,"\u7684\u9012\u5f52\u5b9a\u4e49":43,"\u7684\u964d\u7ef4\u6253\u51fb":43,"\u76ee\u524d\u4e5f\u662f\u4e0d\u652f\u6301\u7684":43,"\u76ee\u524d\u5728\u829d\u5927\u7ee7\u7eed\u505a\u751f\u7269\u5927\u6570\u636e\u76f8\u5173\u7684\u5f00\u6e90\u9879\u76ee":43,"\u76ee\u524d\u6025\u9700\u5e2e\u52a9\u7684\u6709":43,"\u76ee\u524d\u652f\u6301\u4e09\u79cd\u4e0d\u540c\u7a0b\u5ea6\u7684\u7528\u6cd5":43,"\u76ee\u524d\u7684\u4e0d\u8db3\u4e4b\u5904\u8fd8\u6709\u4e00\u4e9b":43,"\u77ff\u5708":43,"\u793e\u4ea4":43,"\u7a0d\u6709\u4e0d\u540c":43,"\u7a33\u5b9a\u7248\u53d1\u5e03\u7684\u524d\u5915\u505a\u4e2a\u5e74\u7ec8\u603b\u7ed3":43,"\u7a33\u5b9a\u7248\u7684\u5404\u79cd\u6761\u4ef6":43,"\u7a7a\u624b\u63a5":43,"\u7b14\u8005\u5de5\u4f5c\u4e0a\u5f00\u53d1\u7684\u4e00\u4e2a\u670d\u52a1":43,"\u7b49":43,"\u7b49\u4f18\u79c0\u7684\u5f02\u6b65":43,"\u7b49\u5230\u9700\u8981\u64cd\u4f5c\u6570\u636e\u5e93\u7684\u65f6\u5019":43,"\u7b49\u591a\u9879\u4fbf\u6377\u529f\u80fd":43,"\u7b49\u5f00\u6e90\u9879\u76ee\u4e14\u4e00\u53d1\u4e0d\u53ef\u6536\u62fe":43,"\u7b49\u6846\u67b6\u7684\u9646\u7eed\u6d8c\u73b0":43,"\u7b49\u9879\u76ee\u4e86\u89e3\u4e86\u5f02\u6b65\u7f16\u7a0b":43,"\u7b80\u5355\u660e\u4e86":42,"\u7c7b":43,"\u7c7b\u4f3c":43,"\u7cbe\u51c6\u63a7\u5236\u52a0\u8f7d\u884c\u4e3a":43,"\u7ec8\u8eab\u4e0d\u5a5a\u578b":43,"\u7edd\u4e0d\u662f\u9ed1\u54c8":43,"\u7edd\u5bf9\u7eff\u8272\u73af\u4fdd\u65e0\u6bd2\u526f\u4f5c\u7528":43,"\u7ef4\u57fa\u767e\u79d1":43,"\u7ef4\u62a4\u793e\u533a":43,"\u800c":43,"\u800c\u5168\u6743\u4ea4\u7ed9\u7528\u6237\u6765\u660e\u786e\u5730\u5b9a\u4e49":43,"\u800c\u662f\u5728":43,"\u804a\u5929\u673a\u5668\u4eba":43,"\u80fd\u53eb\u4e0a\u540d\u5b57\u7684\u50cf":43,"\u80fd\u5728\u5feb\u901f\u539f\u578b\u5f00\u53d1\u4e2d\u5927\u5c55\u8eab\u624b":43,"\u81ea\u7136\u662f\u4f3a\u5019\u5230\u5bb6\u7684":43,"\u867d\u7136\u6587\u6863\u8fd8\u5728\u52aa\u529b\u7f16\u5199\u4e2d":43,"\u867d\u7136\u662f":43,"\u8868":43,"\u8868\u7ed3\u6784\u5b9a\u4e49":43,"\u88ab\u5e7f\u6cdb\u5e94\u7528\u4e8e\u8bf8\u5982\u5b9e\u65f6\u6c47\u7387":43,"\u8981\u7528":43,"\u8bbf\u95ee\u5c5e\u6027":43,"\u8dd1\u8d77\u6765\u4e5f\u662f\u53ef\u4ee5\u98de\u5feb\u7684":43,"\u8f7b\u91cf\u7ea7":43,"\u8fc1\u79fb":43,"\u8fd8\u4f1a\u5076\u5c14\u4fee\u4e00\u4fee":43,"\u8fd8\u4f1a\u8fd4\u56de\u4e00\u4e2a\u5305\u542b\u672c\u6b21\u53d8\u66f4\u7684\u4e2d\u95f4\u7ed3\u679c":43,"\u8fd8\u63d0\u4f9b\u4e86":43,"\u8fd8\u662f\u7b14\u8005\u81ea\u5df1\u5199\u7684\u4e00\u4e2a\u5de5\u5177":43,"\u8fd8\u6709\u51e0\u4e2a\u5546\u7528\u7684":43,"\u8fd8\u6709\u5f88\u591a\u7c7b\u4f3c\u7684\u7279\u6027":43,"\u8fd8\u8d34\u5fc3\u5730\u63d0\u4f9b\u4e86\u4e2d\u6587\u6587\u6863":43,"\u8fd9\u4e2a\u9879\u76ee\u5c31\u53eb":43,"\u8fd9\u4e48\u505a\u9664\u4e86\u80fd\u4fdd\u6301\u719f\u6089\u7684\u5473\u9053":43,"\u8fd9\u4e48\u5b9a\u4e49\u8868\u7ed3\u6784\u751a\u81f3\u8ba9\u4eba\u6709\u70b9\u5c0f\u5174\u594b":43,"\u8fd9\u4e9b\u64cd\u4f5c\u90fd\u4e0d\u4f1a\u8bbf\u95ee\u6570\u636e\u5e93":43,"\u8fd9\u4ee3\u7801\u4f60\u8ba9\u6211\u600e\u4e48\u8c03\u8bd5":43,"\u8fd9\u5bf9\u4e8e\u4e00\u6b3e\u4f18\u79c0\u7684\u5f02\u6b65":43,"\u8fd9\u5c31\u662f":43,"\u8fd9\u662f\u8c01":43,"\u8fd9\u91cc\u7684":43,"\u8fde\u63a5\u6c60\u7ba1\u7406\u548c\u61d2\u52a0\u8f7d":43,"\u901a\u8fc7":43,"\u90a3\u4e3a\u4ec0\u4e48\u975e\u8bf4":43,"\u90a3\u5c31\u662f":43,"\u90e8":43,"\u90fd\u662f\u53ea\u5728\u5185\u5b58\u91cc\u4fee\u6539\u5bf9\u8c61\u7684\u5c5e\u6027":43,"\u90fd\u6709\u53ef\u80fd\u89e6\u53d1\u4e00\u5927\u5806\u610f\u60f3\u4e0d\u5230\u7684\u6570\u636e\u5e93\u8c03\u7528":43,"\u914d\u5408\u4e00\u4e2a\u76f4\u89c2\u7684\u52a0\u8f7d\u5668":43,"\u91cc\u7684\u5bf9\u8c61\u6620\u5c04\u4e0d\u80fd\u4e22":43,"\u91cd\u89c6\u5f00\u53d1\u6548\u7387\u7684\u6982\u5ff5\u5bf9\u4e8e\u5199":43,"\u957f\u671f\u6d3b\u8dc3\u7684\u8d21\u732e\u8005\u8fd8\u80fd\u83b7\u8d60\u4ef7\u503c":43,"\u968f\u4fbf\u4e00\u53e5":43,"\u968f\u7740":43,"\u968f\u7740\u8fd9\u51e0\u5e74":43,"\u975e\u5178\u578b\u5f02\u6b65":43,"\u9879\u76ee\u5916":43,"\u9879\u76ee\u6216\u591a\u6216\u5c11\u90fd\u4f1a\u9047\u5230":43,"\u9879\u76ee\u7684\u8d21\u732e":43,"\u9886\u57df\u7684\u7a7a\u767d":43,"\u9ad8\u6027\u80fd\u6a21\u677f\u9879\u76ee":43,"abstract":[4,24,33,41],"boolean":[11,29,33],"break":[1,3,15,41,44],"case":[2,3,4,6,10,12,13,14,15,21,26,41],"class":[2,6,7,10,11,12,13,14,21,22,23,24,26,27,28,29,32,33,34,35,36,38,41,43,44,45],"default":[2,3,4,6,7,8,11,12,13,14,15,19,21,23,24,26,27,29,32,33,38,39,41,44,45],"enum":[26,27,41],"export":[6,8],"final":[1,2,4,10,29,41,44,45],"float":[11,26],"function":[2,8,11,20,22,26,27,29,33,44],"ila\u00ef":41,"import":[1,2,3,4,6,7,10,11,12,13,14,20,21,24,31,33,35,38,39,41,43,44,45],"int":[11,15,38,44],"long":[1,2,3,4,15,29,38,39],"micha\u0142":41,"new":[1,2,3,4,6,7,8,10,12,14,21,22,23,24,26,29,33,35,38,41,42,45],"null":[2,41],"public":14,"ram\u00edrez":43,"return":[1,2,3,4,7,10,11,12,14,15,21,22,23,24,26,27,29,33,35,38,39,41,44,45],"sebasti\u00e1n":43,"short":[2,3,4,7,14],"static":[28,44],"super":[4,12],"switch":[1,2,3,21,41],"transient":2,"true":[2,3,4,6,7,10,11,12,14,15,21,23,24,26,27,28,29,33,34,38,39,41,43,44,45],"try":[1,2,3,4,10,15,29,36,41,44,45],"var":8,"while":[1,2,4,10,12,14,21,23,29,36,41,45],"za\u0165ko":41,Added:[7,19,24,41],And:[1,2,3,6,7,8,11,12,23,41,44,45],Being:4,But:[1,2,3,4,12,44],Doing:4,For:[1,2,3,4,8,10,12,14,21,23,29,33,36,41,44,45],IDE:43,IDs:12,INTO:[15,45],NOT:[12,24],Not:[10,16,24,33,43,45],One:[1,5,6,29],PRs:44,That:[1,2,3,4,6,12,15,38,44,45],The:[0,2,4,7,8,10,11,12,13,14,15,21,22,23,24,26,27,29,33,36,38,39,41,43,44,45],Their:24,Then:[2,3,4,7,8,12,14,29,44,45],There:[1,2,3,4,13,14,29,41,45],These:[4,7,41],Use:[5,10,23,45],Used:[24,41],Using:[10,11,12,38],WITH:8,Will:44,With:[1,2,3,4,12,23,44,45],Yes:2,__all__:41,__attr_factory__:24,__init__:[12,44],__main__:38,__metadata__:24,__model__:41,__name__:[24,38,44],__repr__:38,__table__:[14,23,24],__table_args__:[24,41,45],__tablename__:[6,7,10,11,12,14,24,38,41,43,44,45],__values__:24,_base:27,_bind:10,_child:12,_children:12,_engin:[26,27],_event:[26,27],_floattyp:26,_idx1:45,_idx2:45,_integertyp:26,_is_metadata_oper:34,_lastrowid:26,_matchtyp:26,_name_idx:10,_numerictyp:26,_parent:12,_pk:45,_sa:26,_schematranslatemap:29,_test:44,_update_request_cl:41,abandon:41,abcd:8,abil:[12,41],abl:[3,4,44],abnormal_detect:11,abort:38,about:[1,2,4,6,8,10,14,15,22,23,29,41,45],abov:[1,2,3,4,7,10,12,33,44,45],absolut:[2,4],accept:[2,3,7,21,23,26,27,29,41,45],access:[0,2,3,10,14,15,24,29,36,38,41,44,45],access_log:11,accessor:26,accid:2,accord:[3,4,29,33,39],achiev:[1,10,12,14,21,29],acid:3,acquir:[2,3,4,14,21,26,27,28,29,36,38,41],acquisit:4,across:[7,33,44],act:[1,2,45],action:[8,36],activ:[7,8,29,44],actual:[1,2,3,4,7,12,14,15,23,24,29,38,44,45],adapt:[3,45],add:[1,2,3,4,6,8,10,12,21,22,23,31,41,42,45],add_child:12,add_us:44,added:[3,7,21,24,29,44,45],addit:[1,2,3,7,11,12,33],addition:[4,23],address:[6,14],adjac:10,adjust:44,admin:5,ado:7,adopt:41,advanc:5,advic:4,affect:[3,7,22,23,41,45],after:[1,2,3,4,6,7,14,15,21,23,24,26,27,29,33,38,41,44,45],after_get:11,afterward:[26,27],again:[1,2,3,4,10,12,29,44,45],against:8,age:[11,23,36],age_idx:11,aggreg:23,aintq:43,aiocontextvar:[2,5,17,18,19,41,43],aiohttp:[41,43],aiomysql:[17,18,19,25],aiomysqldbapi:26,aiomysqldialect:26,aiomysqlexecutioncontext:26,aiomysqliter:26,alemb:[5,11,14,21,42,43,45],alembic_sampl:6,ali:10,alia:[10,12,21,22,23,26,27,28,29,33,35,41],aliasload:33,alik:41,all:[1,2,3,4,6,7,8,10,11,12,14,15,21,23,24,26,28,29,33,36,38,39,41,44,45],all_us:45,allow:[1,2,3,14,21,24,26,27,41,44,45],alon:2,alpha:[10,41],alpin:[8,44],alreadi:[1,3,4,12,24,45],alright:1,also:[1,2,3,4,6,7,10,12,14,15,21,22,23,24,26,27,29,31,33,41,44,45],altern:[2,10,13,29,44,45],although:[3,12],alwai:[2,3,6,8,10,14,21,23,29,33,36,38,41,44,45],amaz:[10,45],among:3,amount:2,analysi:44,andrei:43,ani:[1,2,3,4,6,8,10,21,24,26,27,29,31,33,41],anoth:[1,2,4,12,22,23,29,33,45],answer:[1,3,10],anyth:[4,8,14,29,41],anywai:3,api:[0,2,4,5,10,14,17,19,22,26,27,29,36,42,43,45],apirout:44,apk:44,app:[4,10,13,38,39,41,44],appear:[1,3],append:23,append_where_primary_kei:23,appli:[2,3,5,14,23,29,36,43,44,45],applic:[4,7,10,21,39,41,44,45],appreci:8,approach:[1,3,4,7,10],arbitrari:4,archlinux:43,arg:[19,21,23,24,26,27,28,29,34,36],argument:[2,3,7,10,12,19,21,22,23,26,27,29,33,35,39,41,45],around:[3,29,43],arq:43,arrai:[11,27,41],arrayproperti:[11,32],arriv:1,arrrrh:1,articl:[3,8],artifact:44,asap:1,ascend:12,ascii_lett:[10,12],asgi:44,ask:[2,5],assembl:[2,12],assert:[15,24,36,41,44,45],assertionerror:41,assign:[2,26],assist:12,associ:[2,14],assum:[1,3,4,7,44],assumpt:[3,36],async:[0,1,2,4,7,10,12,14,15,21,23,26,27,28,29,34,35,36,38,41,43,44,45],async_execut:[26,27,28],async_main:3,asyncdialectmixin:[26,27,28],asyncenum:[26,27],asynchron:[0,2,3,12,14,15,16,21,29,36,45],asyncio:[0,1,2,3,5,12,14,16,20,43,45],asyncpg:[2,3,4,10,13,14,15,16,17,18,19,25,29,35,39,41,43,44,45],asyncpg_deleg:41,asyncpgcompil:27,asyncpgcursor:27,asyncpgdbapi:27,asyncpgdialect:[3,27],asyncpgexecutioncontext:27,asyncpgiter:27,asyncpgjsonpathtyp:27,asyncpgsa:[14,43],asyncschemadropp:34,asyncschemagener:34,asyncschematypemixin:34,asyncvisitor:34,ath:23,atom:23,attack:44,attent:38,attribut:[3,10,12,14,23,24,29,33,41,45],attributeerror:21,audienc:45,audit_profil:11,aur:43,austin:10,auth_plugin:26,authent:4,author:[4,21,43,44],author_id:43,auto:[0,11,23,24,44],autocommit:[0,4,26],autogener:[6,44],autom:[12,44],automat:[3,7,12,20,22,23,24,29,33,35,36,38,44],avail:[2,5,10,14,15,23,24,29,36,39,41,44],averchenkov:41,avoid:[3,4,33],awai:[2,4],await:[1,2,3,4,7,10,11,12,14,15,21,23,29,33,36,38,39,41,43,44,45],await_onli:3,awar:44,awesom:[1,43],back:[1,2,3,4,12,15,21,29,36,41],backend:44,background:[3,16],backport:[2,10,41,44],backward:41,bad:[3,41],bake:[5,19,21,22,41],baked_queri:[26,27,28],bakedqueri:[5,22],bakeri:[5,17,18,19,21,26,27],balanc:[4,23],bar:1,barancsuk:41,bare:[1,4,5],base:[3,6,10,12,13,17,18,19,21,22,23,24,25,26,27,29,30,32,33,34,35,36,44],base_exp:32,basedbapi:[26,27,28],baseexcept:[15,36],basemodel:44,basic:[2,3,4,5,23,42,43],batch:[5,23,45],bayer:[4,43],becaus:[1,2,3,4,7,10,14,15,21,23,36,41,44,45],becom:[2,3,14],been:[1,2,26,27,41],befor:[1,2,3,4,7,8,10,12,14,15,22,23,26,29,36,38,44,45],before_set:11,begin:[1,3,4,7,26,27,28],beginn:45,begun:3,behav:[23,41],behavior:[2,3,10,15,23,29,38,41],behind:[2,3,4,7,12,23,41,44],being:[1,2,3,4,14,23],belong:15,below:[8,10,11,44],benefici:4,besid:10,best:[1,8,41,44],beta:41,better:[4,41,43],between:[1,2,4,41],beyond:4,biginteg:[21,38,44],bin:44,binari:[28,41],bind:[2,4,7,10,14,15,21,22,23,26,27,34,41,45],bind_processor:[2,27],bindparam:7,bindtempl:27,binghan:41,birthdai:11,bit:[1,8,14,23,41,45],bite:[29,38],black:41,blade:1,blob:43,block:[1,2,3,4,15,29,36,38],blog:8,blue:2,bondar:43,book:[10,21,43,45],booker:45,bookings_idx_booker_room:45,bookings_idx_day_room:45,bookings_pkei:45,bool:[11,44],booleanproperti:[11,32],boost:[1,7],borrow:[2,15,29,38,39],boss:1,bot:43,both:[2,3,4,10,12,14,21,23,24,26,27,36,41,45],bottleneck:[1,4],bound:[1,21,23,24,26,38,45],boundari:3,branch:8,bridg:3,brien:41,broken:41,browser:8,brutal:1,bryanforb:43,bsd:16,buffer:[3,4],bug:[3,5,10,41,43],bugfix:8,build:[1,3,4,8,10,12,21,23,42],builder:[12,44],built:[3,10,14,16,22,23,24,29,39,41,44,45],builtin:[28,38,41],bulk:[4,5,29],bunch:6,bundl:21,busi:[1,4,14],bypass:3,c10k:1,cach:[7,44],calcul:26,call:[1,2,3,4,7,10,12,15,20,21,23,24,26,27,29,33,36,41,44,45],call_next:10,callabl:[12,26,27,29,33,41],callableload:33,callback:1,caller:10,came:4,can:[1,2,3,4,5,6,7,8,12,13,14,15,21,22,23,24,29,33,36,38,39,41,44,45],candid:41,cannot:[1,2,3,12,41,44],canopi:43,canopytax:43,cap:4,captur:44,care:3,carefulli:1,cast:[11,44],categori:12,categories_1:12,categories_2:12,caught:36,caus:[2,3,4,15,29,38,39,41,45],cdi:43,celeri:4,certain:[4,38],chain:[2,4,7,21,23,33,41,45],challeng:4,chanc:[1,4],chang:[3,6,8,10,19,23,24,29,41,44,45],characterist:3,charset:26,chat:4,check:[2,3,8,10,12,14,23,26,27,44,45],checkfirst:[26,27,34],checklist:44,checkout:8,child:[12,41],child_id:12,children:[12,36],chmod:8,choic:[4,10,12],choos:[4,19,41,45],classic:12,classmethod:[7,23,28,33],claus:[2,10,12,14,21,23,26,27,28,29,33,45],clean:[10,41,44],cleaner:[3,44],cleanli:[4,29],cleanup:39,clear:4,cli:44,click:2,client:[4,8,43,44],client_flag:26,clone:8,close:[2,3,4,15,21,22,26,27,28,29,36,38,41,45],closer:3,cls:[7,11,24],cmd:44,code:[1,2,3,4,10,12,14,15,16,29,36,41,44,45],coin:4,col:12,collect:[23,44],collid:41,color:[2,26,27,28,29],colspec:[26,27],coltyp:[26,27],column:[2,5,6,7,11,12,14,21,23,24,26,27,29,33,38,41,43,44,45],column_kei:27,column_nam:23,columnattribut:24,columnload:[10,12,33],com:[8,10,16,43,44],combin:[2,12,45],come:14,command:[3,4,6,44,45],command_timeout:27,comment:[3,7,29],commit:[0,4,8,15,26,27,28,29,36,41,44],common:[7,14,15,29,39,44],commun:[3,16,43],compani:33,compar:[1,4,23,44],compat:[2,3,10,29,33,41],compil:[7,21,22,28,29,45],compiled_sql:22,complet:[2,10,21,41,44],complex:[1,5,7,12,43,45],compli:3,compliant:3,complic:[0,1],compos:[44,45],composit:23,comput:1,con:0,concentr:4,concept:[2,14,45],conceptu:14,conclus:4,concret:[2,14,24,38],concurr:[1,3,4],condit:[12,23,45],config:[10,13,38,39,41,44],configur:[8,17,33,37,38,44],confirm:41,conflict:[3,12,33],conftest:44,confus:2,congratul:6,conn1:2,conn2:2,conn:[2,3,4,14,21,26,27,28,29,36,41],connect:[0,3,4,5,7,12,14,15,17,19,21,22,26,27,29,34,36,37,38,41,42,44],connect_timeout:26,connection_cl:29,connection_class:27,connectionless:2,conradi:41,consid:[3,4,10,41],consist:41,constant:1,constantli:[3,7],constraint:[24,34,41,45],construct:[4,7,10,14,21,24],consum:[3,4],contain:[3,6,10,24,45],content:[1,17,18],context:[1,2,3,4,10,15,16,21,22,26,27,28,29,33,36,38,39,41,43,44],contextu:[10,12,22,38],contextualgino:10,contextvar:[2,10,17,20,43],continu:[15,36],contrast:[1,4],contribut:[5,41],control:[1,3,5,26,27,29,38,41,44],conv:26,conveni:[2,3,4,14,15,21,23,43,44,45],convers:[2,26,27],convert:33,cool:1,cooper:[0,4],copi:[3,10,29,41,44],core:[1,2,3,5,7,10,16,21,22,24,41,43,45],coroutin:[1,2,3,4,23,29,35],correctli:[3,4,14,15,21,23,41,44],correspond:[2,12,22,23,24,26],correspondingli:[2,15,36],cost:1,could:[1,2,3,4,7,8,10,12,22,23,44],count:[2,10,12,44,45],count_1:45,cours:44,cov:44,cover:[41,44,45],coverag:[41,44],cpu:1,cpython:43,creat:[0,3,4,5,7,8,10,12,14,15,19,21,22,23,24,26,27,29,33,34,35,36,38,39,41,42,43],create_al:[3,7,10,11,12,14,21,34,41,45],create_async:[26,27,34],create_async_engin:3,create_engin:[2,3,4,7,10,13,14,19,21,26,29,33,35,41,45],create_ok:34,create_pool:[2,41],create_t:44,create_task:10,createdb:[44,45],creation:[2,7,10,21,22,41,44],credenti:6,credit:8,critic:1,cross:38,crt:8,crucial:15,crud:[2,4,5,10,12,14,17,18,19,21,29,41,42,43],crudmodel:[21,23,41],csrf:44,ctrl:44,ctx:12,cur:44,curatedlist:43,current:[1,2,3,10,13,15,19,23,26,27,29,41,44,45],current_connect:[0,29,41],current_databas:10,current_us:[4,43],cursor:[2,3,26,27,28,29],cursor_cl:[26,27,28],cursorclass:26,custom:[3,5,11,12,23,24,29,41],customiz:[24,41],cut:4,cutil:41,cve:44,dahlia:43,dai:45,daisi:[11,24,43,45],damn:1,danger:38,darwin:44,data:[1,2,3,10,11,12,23,44,45],databas:[0,2,3,5,6,7,8,11,12,14,15,19,21,23,24,26,27,29,33,36,38,39,41,43,44,45],datastructur:44,date:45,datetim:[10,11,12,24,33],datetimeproperti:[11,32],db_age:23,db_databas:[38,44],db_driver:44,db_dsn:44,db_echo:[10,38,41,44],db_host:[13,38,44],db_kwarg:[13,38],db_name:[6,8],db_pass:8,db_password:[38,44],db_pool_max_s:[38,44],db_pool_min_s:[38,44],db_port:[8,38,44],db_retry_interv:44,db_retry_limit:44,db_ssl:44,db_time:7,db_use_connection_for_request:[38,44],db_user:[8,38,44],dbapi:[26,27,28],dbapi_class:[26,27,28],dbapi_conn:[3,26,27],dbapicursor:[26,27,28],dbname:[10,44],ddl:[34,44],dead:4,deadlock:[3,4],deal:[1,4,10,12,15],debug:[1,38],decent:3,decid:3,decim:41,declar:[1,5,12,17,18,19,21,23,42],declarative_bas:24,declared_attr:[7,11,24,41,45],decod:32,decor:[7,24],decreas:1,deeper:3,def:[1,2,3,4,7,10,11,12,14,24,26,27,38,44,45],default_isolation_level:26,defaultdialect:[26,27],defer:4,defin:[3,5,6,7,11,12,13,14,21,24,29,33,44,45],definit:[10,12,24,44],del:10,delai:4,deleg:[2,14,21,23,41],delet:[12,14,23,41,42,44],delete_us:44,deliv:[1,3],demand:7,demo:44,demonstr:44,depend:[1,2,4,6,7,10,23,29,36,41,42,45],deploi:[4,44],deprec:[3,23,33,41],describ:[4,10,16],descript:[6,8,26,27,28,39,44],design:[2,3,4,10,45],detach:45,detail:[2,8,10,12,45],detect:44,determin:[4,12],deutel:41,dev:[43,44],develop:[6,8,17,38,44],diagram:[1,2,44],dialect:[2,3,10,11,13,16,17,18,19,22,29,34,36,39,41,45],dict:[2,10,11,13,23,24,33,41,44],dictionari:[2,29,38],did:41,didn:[2,4,31],differ:[1,2,3,4,5,11,12,14,21,23,24,29,41,44,45],difficult:1,dig:14,direct:[4,26,29,45],directli:[2,3,4,7,10,12,14,21,23,24,26,29,38,41,44,45],directori:[6,41,44],dirti:[10,41],disabl:[23,41],disable_inherit:41,disable_task_loc:41,disadvantag:4,disallow:10,disast:[15,38],discard:[2,23,29],disconnect:45,discord:43,discourag:4,discuss:10,disproportion:4,dist:41,distinct:[12,23,33,41],distinguish:2,divio:16,django:5,do_load:33,do_on_connect:[26,27],doc:[3,6,8,10,15,41,43,44],docker:[8,44],dockerfil:44,docstr:8,document:[2,3,5,6,7,10,41,43,44,45],doe:[2,3,4,5,12,14,21,23,29,33,41,43,45],doesn:[2,3,4,10,11,12,24,41,44,45],doge:3,doing:[1,4,7,21,41],don:[0,1,2,3,5,10,12,15,23,29,44,45],done:[0,1,2,6,8,10,14,15,22,41,44,45],doubl:1,doubt:12,down:[4,44],downgrad:[6,44],download:16,dramat:1,driven:8,driver:[2,3,10,15,26,27,29,39,41,45],drivernam:44,drop:[1,34,41,44],drop_al:[3,10,12,34],drop_async:[26,27,34],drop_ok:34,drop_tabl:44,dsn:[39,41,44],due:[4,41,45],dure:[1,2,22,24,29,38,41],dynam:[12,14,24],dziewulski:41,each:[1,2,3,4,6,7,10,12,22,24,29,33,38,45],earli:[3,15,16,17,36,39],earlier:[3,10,39],easi:[2,3,10,12],easier:[1,3,7,8,10],easili:[1,2,3,4,12],echo:[2,3,10,29,39,41,44],ecosystem:[3,4],edg:1,edit:11,effect:[19,23,29,45],effici:[1,2],effort:4,either:[1,2,3,4,6,7,10,14,15,23,29,36,44],elem:[21,22,28],element:21,els:[2,3,4,10,14,15,29,38,44,45],email:[4,10],email_address:14,emerg:41,emit:3,empti:[2,38,39,41,44],enabl:[2,8,10,23,24,33,39,41,44],enable_inherit:41,enable_task_loc:41,encapsul:[3,4,10,44],encod:[32,43],encourag:21,encrypt:8,end:[2,3,4,12,15,44,45],endpoint:4,enforc:[3,12,15],engin:[0,1,3,5,15,17,18,19,21,22,23,26,35,36,39,43,44,45],engine_cl:35,engine_from_config:21,enginestrategi:35,enhanc:[4,8,41],enjoi:38,enough:[4,44],ensur:41,enter:[15,21,29],entri:[14,31,44],entry_point:44,enumer:26,env:[6,10,44],environ:[8,41,44],equal:[2,23,41],equival:[23,24,26],error:[3,10,27,28,41],especi:[1,2,4,10,12,41,45],establish:38,etc:[3,7,26,27,44],even:[1,2,3,4,7,8,10,14,21,23,24,29,38,41,44,45],event:[1,3,4,8,26,27,41,43],eventlet:43,eventu:[2,3,10,44],ever:[3,36],everi:[1,7,8,12,23],everyon:2,everyth:[2,3,4,10,14,15,38,45],evil:3,exactli:[2,12,21,29,45],exampl:[1,2,3,4,6,7,8,10,12,13,14,15,21,23,29,33,36,38,41,44,45],except:[1,2,3,10,15,17,18,19,27,28,29,36,38,39,41],exchangeratesapi:43,excit:3,exec:8,execut:[0,3,4,5,7,11,12,14,21,22,23,26,27,28,29,33,39,41,45],execute_bak:[26,27,28],executemani:[28,29,41],execution_ctx_cl:[26,27],execution_opt:[2,3,7,10,12,21,22,23,26,29,33,41],executioncontextoverrid:[26,27,28],exhaust:[2,4],exist:[2,3,4,5,21,23,24,26,27,39,41,45],exit:[15,21,29,36],exp:11,expect:[3,33],experiment:[12,23,33],explain:[3,4,8,10,12,16,44],explan:[16,43],explicit:[0,2,3,10,12,15,26,41,43,45],explicitli:[2,3,4,7,10,11,36,38,45],explict:14,expos:[14,21,41],exposur:3,express:[5,11,23,29,33],ext:[3,10,13,17,18,19,21,38,39,41,44],extens:[2,7,10,13,14,17,21,31,38,39,41,42,45],extern:[1,45],extra:[1,3,33,41,44],extract:41,extrem:2,f2c273a43a3ed893a767d4239046f2befabf510d:43,face:4,facebook:43,facil:26,fact:[3,10,26,27],factori:[22,24,39,44],fail:[2,21,41,44],fair:1,fals:[2,3,4,7,11,14,23,24,26,27,28,29,34,38,39,41,44],familiar:45,famou:1,fantix:[23,43,44,45],far:[3,14,45],fast:[4,38,43,44],fastapi:[4,10,42,43],faster:[7,22,45],featur:[2,3,5,12,23,33,41,44,45],fed:[7,12,41],feed:[1,29,33,44],feedback:5,feel:[1,3,23,29,44,45],fetch:[1,29],fetchal:3,fetchval:3,few:[2,4,10,29,38,41],field:[10,11,14,23],file:[1,6,8,41,44],fill:33,filter:[23,45],find:[3,4,6,24,31,45],fine:[3,4,10,14],finish:[1,2,4,6,23,29,38,44],first:[1,2,3,4,5,7,8,10,12,14,19,21,23,26,27,28,29,33,38,39,41,44,45],first_connect:[26,27],first_nam:10,first_or_404:41,firstli:12,fit:2,five:41,fix:[3,4,5,10,41,44,45],fixtur:44,flag:[26,27,39],flake8:8,flat:10,flexibl:[11,12,43],flow:4,flush:4,fly:5,focu:10,folder:6,follow:[2,3,4,10,11,15,21,33,36,44,45],footprint:1,forc:[1,4],foreign:[10,12,23],foreignkei:[10,12,14,43],forev:[3,23,29],forget:[2,3,45],fork:8,format:[10,26,38],former:2,fortun:4,forward:[12,26,27,28],found:[2,4,10,12,21,22,24,41,44,45],foundat:[4,43],founder:45,founding_us:45,fouser:21,framework:[1,7,10,41,44],free:[4,16,23,29,44,45],freebsd:43,freez:4,frequent:[5,12],friendli:[41,45],from:[1,2,3,4,6,7,10,11,12,13,14,15,16,21,22,23,24,29,33,36,38,39,41,43,44,45],fromclaus:33,frozen:44,frustrat:3,full:[2,6,10,44],full_path:6,fulli:[29,44,45],fullnam:14,fun:[4,45],func:[10,12,33,45],func_or_elem:[21,22],fundament:[3,4],further:[2,3,7,14,29,45],furthermor:[2,7,29,38],fut:10,futur:[3,10,12,44],gain:12,galden:41,garciasilva:43,gbasic:43,gcc:44,gener:[1,2,11,12,23,24,26,27,33,44],geoalchemi:43,get:[1,2,3,4,5,7,10,12,14,16,19,21,22,23,24,29,33,38,41,42,43,44],get_affected_row:[26,28],get_app:44,get_column:33,get_current_connect:41,get_event_loop:45,get_from:33,get_isolation_level:[26,27],get_lastrowid:[26,28],get_loc:41,get_now:2,get_or_404:[38,41,44],get_profil:32,get_raw_connect:29,get_result_proxi:28,get_statusmsg:[26,27,28],get_us:[38,44],get_vers:19,getattr:44,getlogg:44,getter:[7,21],gevent:43,gino:[2,3,4,5,6,8,11,12,13,15,17,18,38,39,42],gino_db:8,gino_fastapi_demo:44,gino_fastapi_demo_test:44,gino_starlett:31,ginoconnect:[2,14,15,17,22,29,36],ginoengin:[2,10,14,15,21,22,23,29,33,35,36,41],ginoexcept:30,ginoexecutor:[2,7,21,22],ginonulltyp:[26,27],ginopool:41,ginoschemavisitor:[14,21,34],ginostrategi:35,ginotransact:[15,29,36,41],git:[8,29,44],github:[8,10,16,43],gitignor:44,gitlab:43,gitter:16,give:[1,3,4,12,22,33],given:[2,3,8,10,12,21,22,23,24,26,27,29,33,35,44],global:[7,14,21,45],gmail:44,gnu:43,goe:45,going:3,golden:4,goncharov:41,gone:41,good:[1,3,29],got:1,gotta:1,grab:3,grai:2,grammar:[4,10,41],grandson:12,grant:3,great:[4,6,10],greater:[1,20],greatli:[1,2,3,8],green:[1,2],greenlet:[3,10],greenlet_spawn:3,group_bi:[10,12],guarante:[4,15,29,44],guess:4,guess_model:41,guid:[16,44,45],guidelin:5,gunicorn:44,hack:[10,44,45],had:[1,14,24],hadn:1,hand:[1,2],handl:[1,2,4,15,29,36,41],handler:38,hang:[3,4],happen:[1,3,4,21,29,45],happi:44,hard:4,hardwar:4,harm:4,has:[1,2,3,4,7,10,12,14,15,19,21,22,23,26,27,29,41,45],has_schema:27,has_sequ:27,has_tabl:[26,27],has_typ:27,hash_:22,haunt:[3,4],have:[1,2,3,4,6,7,10,12,14,15,23,33,36,39,43,44,45],haven:44,head:[2,6,44],heavi:3,hei:[1,4],height:11,help:[0,1,8,15,45],helper:7,henc:21,here:[1,2,3,4,7,8,10,11,12,14,15,23,29,36,41,44,45],herebi:21,hidden:[2,3],hide:[2,41],hierarch:[10,12],hierarchi:44,high:[1,4,7],higher:44,histori:17,hit:4,hmm:2,hold:[1,3,10,15,44],holubakha:41,hong:43,honor:44,hood:[2,23,45],hook:[5,10,21,26,27,31],hoorai:3,host:[26,27,39,44],how:[0,1,2,3,6,8,12,15,16,29,43,45],howev:[1,2,3,4,10,14,21,23,29,41],html:43,http:[1,6,8,10,16,29,43,44],hurri:1,hybrid:4,hyper:1,id_val:10,idea:[3,4],ideal:[1,45],ident:[2,3,12,14,23,41,45],identifi:4,idl:[3,4],ignor:[22,44],imag:44,imagin:[1,3],immedi:[2,4,7,19,23,29,36],immut:2,impair:1,impl:44,implement:[2,3,5,24,26,33,44,45],implic:14,implicit:[0,3,4,10,15,21,26,41,43],implicitli:[2,4,7,10,14,15,29,36],importantli:4,importlib:44,imposs:4,improv:[1,41],in_:12,in_queri:23,inc:43,includ:[3,8,10,23,39,41,44,45],include_foreign_key_constraint:34,include_rout:44,incomplet:44,increment:23,index:[1,5,12,24,34,43,45],index_on_nam:10,indic:[24,33,45],individu:[2,3,23,44],infer:23,info:44,inform:[2,7,10,14,15,21,22,29,45],inherit:[2,7,10,15,22,23,29,41,45],ini:[6,44],init:[6,26,27,41,44],init_app:[13,38,39,44],init_command:26,init_kwarg:[26,27],init_pool:[26,27,28],initi:[2,3,5,7,11,14,23,24,26,27,33,35,39,44],initializederror:30,inject:[2,14],inlin:[14,21,27,41],inner:[2,15,26,27],ins:14,insert:[3,5,14,15,23,26,29,41,43,45],insid:6,insist:12,inspir:[12,14],instal:[6,8,39,41,42,44],instanc:[2,4,6,7,10,11,12,14,15,21,22,23,24,26,27,29,32,33,35,41,44,45],instant:23,instanti:[14,23,29,33,44],instead:[1,2,3,4,12,14,15,23,24,29,31,41,44,45],integ:[6,7,10,11,12,14,24,43,45],integerproperti:[11,32],integr:[5,38,41,42],intend:24,intens:[1,4],interact:[44,45],interest:3,interfac:[2,3,5,21,33,41],interfaceerror:27,interfer:41,interim:3,interleav:1,intern:[2,10,12,15,22,23,24,36,41],internet:4,interpret:[12,36],interrupt:1,interv:27,introduc:[3,10,12,41],introduct:42,intuit:4,invalid:38,invent:10,invers:44,invert_get:24,invertdict:24,invok:[23,26,27],involv:[10,14,26],irrelev:21,is_:23,is_local_root:41,isdigit:38,ish:4,isol:[0,2,26,27,29,41],isolation_level:[2,3,26],issu:[3,4,8,10,11,26,41,43,44],item:[12,14,33,34,44],iter:[2,3,10,12,14,21,23,24,26,27,28,29,41,43],its:[1,2,3,4,10,12,21,23,24,26,27,29,33,36,39,41,45],itself:[1,2,3,4,10,12,14,15,24,26,27,44],iuliia:41,jack:14,java:43,jeff:10,jekel:41,jetbrain:43,jim:41,job:29,join:[5,12,21,23,33,41,43],join_queri:12,join_without_n_plus_1:4,jone:[14,43],json:[5,23,26,27,38,41,44],json_support:[17,18,19,23],jsonb:[11,41],jsonindextyp:26,jsonpathtyp:[26,27,41],jsonproperti:[11,23,32],julio:41,just:[1,2,3,4,6,10,12,14,15,23,24,36,38,41,44,45],keep:[1,3,4,8,10,12,26,41,45],kei:[8,10,12,23,24,26,33,41,45],kentoseth:41,kept:[4,33],keyout:8,keyword:[2,3,7,10,12,19,22,23,33,45],kill:[1,4],kind:4,king:[43,44],kinwar:41,know:[1,2,3,4,10,15],knowledg:[12,14,23,45],known:[2,12,44,45],kooten:41,kovalev:41,kubernet:44,kwarg:[19,21,22,23,24,26,27,28,29,34,35,36,39,41],label:[33,41],lacerda:41,lambda:12,larg:[3,4,29],larger:[1,44],last:[1,2,4,12,21,23,36,44,45],last_nam:10,lastli:3,lastrowid:26,later:[1,2,10,12,14,29,39],latest:[23,29,43,44],latter:2,law:4,layer:4,layout:[41,44],lazi:[0,3,10,17,29,37,38,41],lazili:[2,7,19,38],lazy_engin:10,lead:[3,4,11],leaf:12,learn:[2,4,22],least:[1,4,7],leav:[3,29],led:29,left:[2,3,12,23,43],legaci:14,len:12,length:14,lengthi:4,leosussan:43,less:[1,2,4,14],lesson:16,let:[1,2,3,4,7,12,14,15,44,45],level:[0,2,4,7,10,11,14,23,24,26,27,29,45],leverag:[3,4,11],lib:[8,44],libffi:44,librari:[3,41,43,44,45],licens:[16,43],liebig:4,lifetim:38,lightn:44,lightweight:[6,16],like:[1,2,3,4,6,7,8,10,11,12,14,23,24,29,31,38,39,44,45],likewis:[2,12,33,45],limit:[1,3,4,26,27,28,41,45],line:[2,6],linearli:1,link:[3,4,6,10,44],list:[2,8,10,11,14,29,33,44,45],listen:[3,4,26,27],liter:[1,29],littl:[1,8],live:[1,44],load:[2,3,4,5,7,12,21,23,29,33,41,43,44],load_modul:44,loader:[5,10,17,18,19,21,23,28,29,41,43],lobbi:16,local:[2,6,8,17,44],local_infil:26,localhost:[3,4,7,8,10,12,13,14,33,38,39,44,45],locat:[23,41],lock:[3,4,44],log:[44,45],logger:44,logging_nam:[2,29],logic:[1,3,4,44],login:8,longer:[1,2,4,10,29,41],look:[1,4,8,12,23,39,44],lookup:[23,41],loop:[1,3,10,21,26,27,28,29,35,41,43],lose:29,lost:4,lot:[1,3,4,12],love:4,low:[4,7],lower:[1,3,10,45],luckili:3,made:[3,10,12,14,41,44,45],magic:[2,3,7,12,45],magicstack:[10,43],mai:[1,2,3,4,10,11,12,15,21,22,26,29,38,44,45],main:[2,3,4,6,7,10,12,14,44,45],main_app:6,maintain:[3,4,10,24,43,44],mainten:[3,41],major:4,make:[1,2,3,4,6,7,8,10,12,23,26,27,29,38,44,45],make_express:32,make_url:44,manag:[0,1,3,7,15,21,29,36,38,41,44,45],mandatori:14,mani:[2,3,4,5,8,10,26,27,28,29,41,45],manipul:41,manual:[3,5,10,11,12,14,23,29,36,41,44],map:[2,10,12,14,43,44,45],mapper:10,marissa:10,mark:[4,24,29],martin:41,masonri:44,mass:45,massiv:23,master:43,match:[3,12],matchtyp:26,matter:[2,3,12,14],max:[2,44],max_cacheable_statement_s:27,max_cached_statement_lifetim:27,max_inactive_connection_lifetim:27,max_overflow:4,max_queri:27,max_siz:27,maximum:39,maxsiz:26,mayb:3,mean:[1,2,4,12,15,23,24,26,27,29,41,45],meaningless:[2,24],meant:[2,45],meanwhil:[7,23,36,41],meet:8,member:45,memori:[1,2,10,23,24,29,45],mention:[1,2,3,14,44,45],mess:[1,21],messag:[4,41],met:45,meta:3,meta_path:31,metaclass:23,metadata:[2,7,10,14,21,22,23,24,34,41,43,44,45],method:[2,3,4,7,14,21,22,23,24,26,27,29,33,36,41,45],michael:43,middl:[1,39],middlewar:[10,39,41],might:[1,2,3,8],migrat:[5,17,44],mike:4,mimic:3,min:44,min_siz:[2,27],mind:1,minhe:43,minim:[1,14],minimum:4,minsiz:26,misc:41,miser:4,miss:[2,3,41],mission:4,mistak:41,mix:[3,4],mixin:[24,41,45],mkdir:44,mkvirtualenv:8,mock:33,mod:44,mode:[2,3,4,26,27,29,36,39,44],model:[2,4,5,6,7,10,11,14,21,23,24,28,29,33,38,41,42,43],model_base_class:21,model_class:[21,24],model_kei:10,modelload:[10,12,33,41],modern:4,modif:45,modifi:[3,12,44,45],modul:[2,6,10,17,18,41,44],modulenotfounderror:6,moment:[1,2,4],more:[1,2,3,4,7,8,10,12,14,15,21,22,23,24,29,41,45],morgan:41,most:[2,3,4,10,14,15,23,26,29,41,45],mostli:[3,4,23],move:[3,41,45],much:[1,2,3,4,11,14,15,29],multi:1,multiparam:[2,21,28,29,41],multipl:[1,2,5,12,21,22,29,33,41,45],multipleresultsfound:[29,30],multiplex:1,multitask:[0,4],musl:44,must:[1,2,3,4,6,10,14,21,29,33,44,45],mutabl:41,my_app:6,myapp:44,mydb:44,mydb_test:44,mydialect:[26,27],mykyta:41,mymodel:44,myself:[1,4],mysql:[10,26,43,45],mysqlcompil:26,mysqldialect:26,mysqlexecutioncontext:26,mytab:15,mytabl:15,myuser:23,name:[1,2,3,4,6,7,8,10,11,12,14,21,23,24,33,35,38,39,41,43,44,45],name_or_url:35,namespac:31,narrow:8,nativ:[3,11,41],natur:[1,4],neal:41,nearli:1,neat:4,neath:3,necessari:[1,4,26,27,38],necessarili:2,need:[1,2,3,4,6,7,8,10,11,12,14,15,19,21,29,38,39,41,44,45],neither:22,nest:[2,3,5,12,29,33,36],network:[1,2,4],never:[1,4,14,15,29,45],nevertheless:4,new_child:12,new_nam:10,new_names_dict:10,newcom:16,newer:3,newli:[23,26,27,45],next:[1,3,4,6,14,26,27,28,29,44],nicknam:[6,14,38,44,45],no_delai:26,no_deleg:21,non:[1,10,13,14,29,41,45],nonam:[6,14,45],none:[2,3,10,12,14,21,22,23,24,26,27,28,29,32,33,34,35,39,41,44,45],none_as_non:[17,23,33],nor:22,noresultfound:[29,30],normal:[2,3,4,7,11,12,14,15,21,23,24,36,44,45],nosuchrowerror:30,note:[2,3,12,19,26,29,38,41,42,45],noth:[1,2,3,4,10,15,33,41],notic:[1,2,12],now:[1,2,3,4,6,7,8,10,12,14,15,16,21,29,33,41,44,45],nullabl:[11,14,44],nullpool:[13,27,41],nulltyp:[26,27],number:[2,4,11,23,39,44],numer:[26,28],obj:34,object:[2,3,4,6,7,10,11,12,14,21,22,23,24,26,27,28,29,32,33,34,36,41,43,44,45],objectproperti:[11,32],obviou:[1,4],obvious:[4,41],occasion:[4,21],occur:[1,26,27],odd:3,off:[2,3,4,38,41],offer:[2,14,36,41],offici:[6,8,21,31],often:[21,23,24],okai:[1,44],olaf:41,old:[3,23,43],olexii:41,omit:[12,23],on_claus:[23,33],on_connect:[26,27],onc:[1,2,4,6,10,12,22,24,26,27,29,45],one:[1,2,3,4,7,10,12,14,16,21,23,26,27,28,29,33,41,45],one_or_non:[2,7,14,21,29,41],ones:[2,3,4,21,33,44,45],onli:[1,2,3,4,6,7,12,13,14,15,16,21,22,23,24,26,29,33,36,39,44,45],ons:2,open:[3,6,8,15,29,43],openid:4,opensourc:43,openssl:[8,44],oper:[1,2,4,8,21,23,24,29,41,42,43,44],opposit:[4,29],ops:3,opt:29,optim:2,option:[1,2,3,7,10,12,13,21,22,23,26,27,29,33,41,44],order:[1,2,3,10,12,23,29,44,45],ordinari:12,org:[6,43],origin:[3,8,14,23],orm:[0,2,3,5,12,16,43,45],orphan:2,orz:43,oss:43,other:[1,2,3,4,5,6,8,13,14,15,21,23,24,29,33,39,41,44,45],otherwis:[24,26,27,29,45],our:[1,4,6,44,45],out:[1,2,4,8,14,24,29,45],outer:[2,12,15,23],outerjoin:[10,12,33,41,43],output:[3,12,14],outsid:[1,45],over:[4,44],overal:[4,12],overhead:[1,4],overlap:1,overload:4,overrid:[7,21,23,24,41,44],overwrit:[3,44],own:[1,2,4,10,12,13,14,44,45],owner:8,packag:[6,17,18,39,41,43,44],page:1,pai:38,pair:[12,23],parallel:[1,2],param:[21,28,29],paramet:[2,5,7,14,21,22,23,24,26,27,28,29,33,39,41,45],paramref:26,paramstyl:[2,26,28],parent:[2,10,12,14,21,36,41],parent_id:[10,12,41],parents_x_children:12,parentxchild:12,pars:[29,41],part:[1,2,3,4,8,10,26,44,45],partial:[2,10,41],particular:[26,27],particularli:23,pascal:41,pass:[1,7,8,26,27,29,33,39,41,44],passfil:27,passin:8,passiv:41,passout:8,password:[6,8,26,27,39,44],patch:[10,20,41],patch_asyncio:20,patch_schema:34,path:44,pattern:[4,10,12],paus:1,pavol:41,payload:2,pem:8,pend:[3,17,23],peopl:[3,4,12],pep:[3,41,43],per:[3,26,27,29],perform:[1,4,7,10,38],perman:[2,3,29,39,41],persist:3,person:3,peter:43,pgcompil:27,pgdialect:[3,27],pgexecutioncontext:27,pgjone:43,phase:44,philip:43,pictur:3,piec:[1,12,45],pip:[6,8,39,41,44,45],place:[3,24,26,29,41],plai:[2,4,21,29],plain:[2,10,43],plain_old_java_object:43,platform:[4,44],pleas:[1,2,3,7,8,10,11,12,14,15,19,21,29,38,41,44,45],plu:[7,21],plugabl:44,pluggi:44,plugin:44,plural:45,poetri:[6,41,44,45],point:[1,3,4,29,31,44],poli:24,pool:[2,3,4,5,7,22,26,27,28,29,38,39,41,44],pool_class:[13,26,27,28],pool_max_s:[39,44],pool_min_s:[39,44],pool_recycl:26,poolev:[26,27],pop_bind:[2,21,41,45],popo:43,popul:[23,45],popular:[44,45],port:[3,4,14,26,27,39,44],posit:[7,23,26,27,29,33],possibl:[1,2,3,4,6,7,8,12,14,22,26,27,33,38,41,44,45],post:[2,3,8,12,44],post_exec:26,postfetch_lastrowid:26,postgi:43,postgr:[6,8,10,35,38,39,44],postgreserror:27,postgresql:[2,3,4,7,8,10,11,12,13,14,21,27,33,35,36,41,43,44,45],postgresqlimpl:44,postprocess:29,potenti:[3,12],power:45,practic:[1,3,4,29,45],pre:[3,19,22],prebak:[7,19,26,27],predict:1,preemptiv:1,preexecute_autoincrement_sequ:26,prefer:[4,26,33,38,45],prefetch:41,prefix:35,prepar:[5,6,19,22,26,27,28,29,41],preparedstat:[27,28],present:[2,12,29,41],press:44,pretti:[4,14],prevent:[7,41],previou:[1,2,12,14,21,39,41],previous:[1,2,12,44,45],primari:[23,26,41,45],primary_kei:[6,7,10,11,12,14,21,24,38,43,44,45],primarykeyconstraint:[44,45],primit:3,princip:[3,4],print:[3,5,7,11,12,14,23,43,45],prioriti:[4,44],privkei:8,pro:0,probabl:[2,3],problem:[1,3,4,6,16,41],proce:[26,27],process:[1,2,12,26,27,41,44,45],process_row:28,processor:[12,41],procur:26,produc:[2,12],product:[0,42],profil:[11,32,41],program:[0,2,3,4,12,15,45],program_nam:26,progress:2,prohibit:15,project:[6,8,10,42,45],promis:12,prop_nam:[11,24,32],propag:15,proper:[3,4,22],properli:44,properti:[2,4,5,6,10,12,14,21,22,26,27,28,29,33,36,41,45],propos:8,protect:41,provid:[1,2,3,7,10,11,12,14,15,21,22,23,29,31,33,39,41,44],proxi:[23,26],psql:8,psycopg2:[2,3,44],psycopg:44,publicli:[21,29],pull:5,pure:[10,41,45],push:8,put:[2,3,8,14,36],pwd:8,pycharm:43,pydant:44,pypi:[16,41],pyproject:44,pytest:[8,44],python:[1,2,3,4,6,8,10,11,16,20,24,31,41,42,44,45],pythongino:43,pythonpath:[6,44],qbasic:43,qualiti:4,quart:[41,43],queri:[0,4,5,8,11,12,14,17,19,21,22,23,24,26,27,28,29,33,39,43,45],query_cl:22,query_executor:21,query_ext:21,query_param:10,querymodel:23,question:[3,4,5],queue:[3,4,22],quick:[5,44],quickli:[3,10],quit:[2,3,4,10,14,23,44,45],qulaz:41,rais:[2,10,15,21,29,36,41],raise_commit:[15,36],raise_for_statu:44,raise_rollback:[15,36],raiseerr:44,ran:1,randint:[10,12],random:[10,12],rang:[10,12,41],rather:[1,4,10,23,45],raw:[1,2,4,5,12,13,14,22,29,33,41,45],raw_conn:[26,27,28],raw_connect:[29,41],raw_pool:[26,27,28,29,41],raw_transact:[15,26,27,28,36],rdbm:[38,45],reach:[36,38],reaction:4,read:[1,2,3,10,21,22,23,24,29,45],read_commit:3,read_default_fil:26,read_default_group:26,read_root:4,readabl:1,readi:[1,8,44],readm:[8,41],real:1,realiti:4,realli:1,reason:[3,4,26,27],receiv:[1,3,10,26,27],recent:[2,29,45],recogn:[3,15,23,41],recommend:[2,21,29,38,45],reconsid:4,record:[3,41,45],recov:41,recurs:[12,29],recv:1,red:1,redirect:24,reduc:[12,14,44],refactor:41,refer:[2,10,12,15,16,21,29,45],referenc:[5,29,36],reflect:24,refresh:41,regardless:[3,44],regist:1,regular:[3,12,14],reinvent:4,rel:6,relat:[2,10,12,43,44],relationship:[4,5,23,29,41],releas:[2,10,17,26,27,28,29,38,39,45],relev:[21,29,41],reli:4,reliabl:[3,4,44],reload:[23,32,44],remain:[10,41,44,45],rememb:[4,8,23,44,45],remind:4,remov:[2,23,41],renam:41,repeat:1,replac:[2,3,24,33,39,41],repli:4,repo:8,report:5,repositori:44,repr:[26,27,28,29],repres:[10,21,22,29,36,45],represent:41,reproduc:8,req:8,requeijo:41,request:[4,5,10,23,38,39,44],requir:[1,2,3,4,8,14,21,23,24,29,41,44,45],requirements_dev:8,reserv:3,reset:[3,41],reset_loc:41,reskov:41,resourc:[1,2,4],respond:4,respons:[1,12,29,38,39],rest:[11,23,36,38,44,45],restart:44,restor:3,restrict:21,result:[1,2,3,5,12,21,22,24,26,27,29,33,41,45],result_processor:[2,26,27],resultproxi:26,resum:[1,4,21],retri:44,retriev:[2,12,23,42,44],retry_interv:44,retry_limit:44,return_model:[2,21,23,28,29],reus:[0,4,12,15,29,33,38,41],reusabl:[0,29],revamp:41,reveal:3,revers:[2,14,29],revert:[2,41],review:[3,41,44,45],revis:[5,11,44],rewritten:[14,41],rewrot:41,ricardo:43,rich:[12,41],right:[0,3,44],risk:[2,3,14,45],riski:3,roald:41,role:8,roll:[3,15,29,36],rollback:[3,4,15,26,27,28,29,36,41],roman:41,room:45,root:[4,29,44],rootdir:44,roughli:44,rout:38,router:44,row:[2,3,7,10,12,14,23,26,27,28,29,33,41,45],rowproxi:[2,14,33,41],rsa:8,rst:8,rule:[2,14,15,29,33],run:[1,2,3,4,5,6,7,8,11,12,29,33,38,39,44,45],run_sync:3,run_until_complet:45,runtim:[7,44],sa_conn:29,sacrific:4,safe:[1,14,29],safe_connect:3,sai:[1,3,4,38,44,45],said:[1,4],same:[1,2,3,4,5,6,7,11,12,14,15,21,23,24,29,33,38,39,41,44,45],sampl:[3,6,44],sanic:[13,17,37,41,43],sanicframework:43,saniti:3,save:[1,2,29,32,44],savepoint:[15,36],scalabl:4,scalar:[2,3,4,7,10,14,21,23,28,29,41,45],scale:1,scenario:[1,2,4,7,12,14,45],scene:[2,3,7,44],schedul:1,schema:[5,6,17,18,19,21,24,26,27,29,43,44,45],schema_ext:21,schema_for_object:29,schema_visitor:21,schemadropp:34,schemagener:34,schemaitem:[21,41],scope:[4,8,38],scott:3,search:1,sec:1,second:[1,2,3,4,12,23,29],secret:44,section:[8,16],secur:[10,44],see:[1,2,3,6,7,8,10,14,15,19,29,44,45],seek:4,seem:[1,14],seen:[3,7],select:[2,3,4,7,10,11,12,14,15,21,23,26,29,33,41,43,45],select_from:[10,12,33],self:[5,10,11,21,23,26,27,29,33,38],send:[3,4,8,27],sens:4,sent:21,sep:12,separ:[4,8,12,15,41,44],sequenc:[2,26,27,34],sequence_nam:27,sequenti:1,sergei:41,seri:[2,29],serializ:3,serv:4,server:[2,4,8,10,29,38,39,41,42,45],server_default:[10,11,12],server_public_kei:26,server_set:27,servic:[43,44],session:[3,4,44],sessionmak:4,set:[2,3,5,7,8,10,12,14,21,22,23,24,26,27,29,33,38,39,41,44,45],set_bind:[2,7,10,21,22,29,41,45],set_except:10,set_isol:26,set_isolation_level:[3,26,27],set_main_opt:44,set_result:10,setattr:[12,33,41],setter:[3,10,12,21],settl:38,setup:[6,8,27,39],sever:[1,2,21,23,29,45],shadow:22,shall:[15,23,38,44],shallow:3,share:[1,2,7,29,38,39,41],sharp:1,shini:44,shortcut:[2,4,12,15,19,21,22,23,29,33,41,44,45],shortest:4,should:[1,2,3,4,8,12,13,21,23,24,26,27,29,31,39,41,44,45],shouldn:[4,41],show:[3,12,44],shown:[2,36],shtrikker:41,shut:44,shutdown:44,side:[2,4,12,29],sign:1,signific:3,silenc:1,simeon:41,similar:[1,2,3,6,7,8,12,14,15,21,23,41,44,45],similarli:[2,12,14,15,23,29,41],simpl:[1,2,4,6,10,14,21,41,42,43,45],simpler:[1,2,45],simplest:33,simpli:[1,2,3,4,10,11,12,21,23,29,33,38,39,44,45],simplic:1,simplif:3,simplifi:[7,44],simul:[2,3,10],sinc:[1,23,41],singl:[1,2,3,4,12,14,23,26,27,29,45],singular:45,situat:[4,29,36],size:[1,44],skip:[15,36,44],sleep:[1,2,4],slice:1,slower:[4,45],small:[1,2],smaller:4,smarter:4,soft:3,softwar:[3,16,43],sole:[26,27],solut:[0,4,31,45],solv:[1,3,4,16],some:[1,2,3,4,6,7,8,10,11,12,14,23,29,39,41,44,45],someth:[1,2,4,12,44],sometim:[2,3,4,38,45],soon:38,sourc:[16,23,29,41,43,44],speak:[2,3,45],special:[12,26,27,38],specif:[2,16,23],specifi:[2,11,12,14,15,23,24,26,27,29,33,39,41,45],split:[1,45],sql:[2,3,4,5,11,12,14,21,22,23,26,27,29,34,41,43,45],sql_mode:26,sqlalchemi:[0,2,4,5,6,7,11,12,14,16,19,21,22,23,24,26,27,29,33,34,35,39,41,43,44,45],sqltype:[26,27],src:[41,43,44],ssl:[5,8,26,27,39,41,44],ssl_cert_fil:8,ssl_key_fil:8,stabil:10,stabl:[3,41,43,45],stack:[2,29,41,44,45],standard:[2,3,4],stare:44,starleet:41,starlett:[10,17,31,37,41,43,44],start:[1,2,3,4,5,7,10,15,16,23,29,36,38,41,42,45],startup:44,starv:0,starvat:4,state:[1,4,45],stateless:[4,10],statement:[2,3,4,5,10,14,15,19,22,23,26,27,28,41,45],statement_cache_s:27,statement_compil:[26,27],statu:[2,3,7,14,15,21,23,28,29,41,45],status_cod:44,stave:4,step:[1,2,3,4,7,8,16,44],stereotyp:4,stick:1,still:[1,2,3,4,10,12,14,22,24,29,33,36,41,44,45],stop:[4,15,38],storag:[24,41],store:[1,2,3,11,12,24,26,44],stori:[0,3],storm:[3,41],str:[4,11,22,33,44],straightforward:4,strang:45,strategi:[2,17,18,19,33],string:[2,7,10,11,12,14,21,22,23,24,26,27,29,38,41,43,45],stringproperti:[11,32],strongli:4,structur:[3,6,12,23,29],stub:43,stuff:[3,45],style:[4,12,14,41],sub:[4,10,24,33,41,44],subclass:[2,14,21,24,33,36,41,45],subj:8,subload:12,submit:[5,44],submodul:[17,18],subpackag:[17,18],subqueri:[23,41],subsequ:3,subset:8,succeed:29,success:[3,6],successfulli:[3,36,45],suffici:3,suffix:7,sugar:4,suggest:[1,45],suit:[23,41],summari:4,support:[2,3,5,8,11,12,16,17,20,21,23,26,29,33,37,41,43,44,45],support_prepar:[26,28],support_return:[26,28],supports_native_decim:[26,27],suppos:[2,7,24,33,36],sure:[1,3,4,8,12,29,38,44],surpris:3,svx:8,swagger:44,sweet:1,symbol:21,sync:[3,10],sync_engin:3,sync_safe_connect:3,synchron:[1,21],syntax:8,sys:31,system:[1,2,8,10,12,44],tabl:[5,6,7,12,14,15,21,23,24,26,27,34,41,44,45],table_nam:[24,26,27],tableclaus:14,tag:8,take:[1,2,4,7,10,13,14,23,29,33],taken:[1,2,29,38],talk:4,target:[44,45],target_metadata:[6,10,44],task:[1,2,4,17,29,39],tast:[14,44],team:[1,23,41],team_id:23,technic:23,telegram:43,tell:[2,3,45],temporarili:3,ten:[1,4],termin:[8,45],test:[3,8,41,42],test_aiohttp:8,test_crud:44,test_gino:8,test_us:44,testclient:44,text:[3,8,10,11,12,14,21,33,41],textual:[2,12],than:[1,2,3,4,7,10,12,13,15,23,24,29,43,45],thank:[1,2,41],thei:[1,2,3,4,7,8,10,12,14,15,23,24,29,41,44,45],them:[1,3,4,6,7,10,11,12,23,41,44,45],themselv:[1,7],theoret:[4,41],therefor:[1,2,4,7,10,14,21,23,29,38,45],thi:[1,2,3,4,6,7,8,9,10,11,12,14,15,20,21,22,23,24,26,27,29,31,33,35,36,38,39,40,41,44,45],thing:[1,2,3,4,6,7,14,24,41],think:[1,3,4,10],third:[12,14],those:[4,29],though:[1,3,4,14,23,38,45],thought:[3,44],thousand:[1,4],thread:[1,2,3,4],three:2,through:[2,5,8,12,15,21,31,41,44,45],throughput:[1,4],thu:[1,2,4,14,15,29,41],tiago:41,tiangolo:43,tiger:3,tim:43,time:[1,2,3,4,6,10,11,12,15,19,21,23,26,29,38,44],timeout:[7,21,23,26,27,28,29,41],timeouterror:29,timestamp:11,timezon:24,tini:4,tip:5,titl:[43,44],to_dict:[23,41,44],togeth:[1,2,3,29,36,44,45],token:4,toml:44,toni:[41,43],too:[1,2,3,4,10,14,15,29,44,45],took:3,tool:[4,6,43,44,45],toolkit:6,top:[2,3,4,10,14,16,29,44,45],tornado:[17,37,41,43],tortois:43,total:[1,4],touch:[15,36],touchabl:2,tox:8,track:[44,45],trackedmixin:24,tradit:[10,14,45],transact:[2,3,4,5,10,12,17,18,19,21,26,27,28,29,38,43,44],transaction_isol:3,transform:12,transit:3,translat:[2,11,12],trap:[15,36],travers:2,traverse_singl:34,treat:[2,3,7,12,24,29,35],tree:12,tri:[1,4,29,41,45],trigger:[1,4,24],trio:10,troubleshoot:8,truncat:45,tupl:[2,10,12,23,29,33],tupleload:[10,12,33,41],turn:[1,2,3,12,29,38,41],tutori:[14,16,43,44,45],twice:[2,5,26,27,45],twist:[4,43],two:[1,2,4,7,10,12,14,15,23,29,36,44,45],tx1:[15,36],tx2:[15,36],tx3:36,txt:8,type:[2,5,10,11,12,14,23,24,26,29,33,36,41,45],type_nam:27,typic:[4,10,26],ua1:12,ua2:12,ubuntu:43,uid:[7,12,44],ultim:4,ultra:43,unbind:21,unchang:45,under:[2,3,10,12,14,21,23,24,33,41,44,45],underli:[2,3,14,15,29,36,41],unfortun:[3,4],unicod:[6,10,12,14,24,26,27,38,44,45],unifi:[2,41],uninitializederror:[10,30,41],uniqu:[2,12,45],unique_constraint:24,unique_id:24,uniqueconstraint:24,unix:43,unix_socket:26,unknown:[12,41],unknownjsonpropertyerror:30,unless:[2,4,23,45],unlik:1,unnam:[41,44],unnecessarili:38,unpin:41,unpredict:[1,4,38],unrecogn:39,unreli:4,unreus:2,unset:2,unspecifi:23,untest:2,until:[1,10,11,29],untouch:45,unus:[2,4],unwant:14,unwrap:[26,27],updat:[2,3,5,8,11,23,24,29,33,36,41,42,43,44],update_execution_opt:[29,41],updaterequest:[23,45],upgrad:[2,3,6,10,41,44],upon:[3,15,26,27,36],upstream:[3,10],urh:1,url:[2,6,21,26,27,28,35,39,41,44,45],usabl:[2,29,41],usag:[2,5,6,23,29,39,41],use:[1,2,3,4,5,6,8,11,12,13,14,15,21,22,23,26,27,29,31,33,36,38,39,41,43,44,45],use_connection_for_request:[39,44],use_unicod:26,used:[1,2,3,7,10,12,13,14,21,22,23,24,26,27,29,33,41,44,45],useful:[2,4,7,14,23,29,33,36],user1:23,user2:23,user:[2,3,4,5,6,7,8,11,12,13,14,21,23,24,26,27,29,33,36,38,39,43,44,45],user_gett:7,user_id:[10,12,14,23,38],user_queri:7,user_t:7,usermodel:44,usernam:[6,44],users_t:2,uses:[3,10,12,23,35,44],using:[1,2,3,4,7,8,10,12,14,15,23,24,26,27,29,33,41,44,45],usual:[1,2,3,4,7,10,11,12,14,21,23,24,29,44,45],utc:12,util:[3,24,31],uuid4:44,uuid:44,uvicorn:[43,44],uvicornwork:44,uvloop:[1,43],v7oze:29,val:[10,11,32],valid:[3,14],valu:[2,3,7,10,12,14,15,21,23,24,26,27,29,32,33,38,41,44,45],valueload:33,van:41,vanilla:[2,14],vargovcik:41,vari:44,variabl:[6,43,44,45],variant:2,venv:44,veri:[1,2,3,4,11,12,14,23,26,27,29,36,38],verifi:3,version:[3,6,7,8,10,19,20,21,22,23,24,39,41,44],view:[26,44],virtual:[3,8],virtualenv:44,virtualenvwrapp:8,visit:[10,12,21],visit_foreign_key_constraint:34,visit_index:34,visit_metadata:34,visit_sequ:34,visit_t:34,visual:43,vladimir:41,volkova:41,volunt:8,wai:[2,3,4,8,10,11,12,14,15,21,29,44,45],wait:[1,3,4,11,15,23,29,44],wang:[41,43],wanna:[1,4],want:[1,2,3,4,5,6,8,14,21,23,29,38,44,45],ware:10,warehous:22,warn:[41,44],wast:[1,2,4],watch:[1,43],weakref:41,web:[1,7,8,10,41,43],websit:8,weird:3,welcom:[8,14,41,43],well:[1,2,3,4,24],were:[1,4],what:[1,2,3,4,5,14,15,21,44],whatev:[2,4,12,23,29],wheel:4,when:[0,1,2,3,6,7,8,10,12,14,15,19,20,21,22,23,24,26,29,33,36,38,41,44,45],whenev:44,where:[2,3,4,7,10,11,12,14,21,23,29,33,36,44,45],wherea:1,whether:[8,29,36,41],which:[1,2,3,4,7,10,12,14,21,22,23,24,26,27,29,36,38,41,44,45],whichev:41,whoever:8,whole:[1,41,45],whose:[3,14,36,45],why:[0,1,12,45],wide:[3,26,27],wider:45,wiki:43,wikipedia:43,wip:[9,38,40],wire:3,wise:[3,4],wish:[2,8,12,29],with_bind:[2,7,10,12,14,21,22,41],with_tabl:[7,24,41],within:[1,3,4,15,26,27,29,38,41],without:[1,2,3,4,7,11,14,15,21,29,41],won:[1,2,3,4,7,10,14,15,22,36,41,44,45],wonder:1,word:[3,45],work:[1,2,3,4,5,6,8,12,14,17,23,24,26,29,37,41,44,45],workaround:[3,10],workdir:44,worker:44,world:[1,3,14,38],worri:[2,4,10,15,41],worth:[2,14],would:[1,3,4,8,10,12,21,23,33],wrap:[3,10,24],wrapper:[2,3,4,10,24,29,43],write:[1,2,4,5,10,12,42,45],written:[14,43],wrong:[1,4,41],wrote:[1,4],ww4ronfhiqi:43,www:43,wysiwyg:3,x509:8,xss:44,xxx:[23,43],xxxx:5,yai:[1,10],yeah:3,year:3,yellow:1,yes:[1,3],yet:[1,10,11,29],yield:[1,4,14,23,24,29,33,44],yml:44,you:[1,2,3,4,6,7,8,10,11,12,14,15,21,22,23,24,29,36,38,39,41,44,45],your:[1,2,3,4,6,8,10,12,14,15,21,29,41,44,45],your_name_her:8,yourdbnam:44,youtub:43,yurii:41,zen:43,zenof:43,zero:[23,29],zone:[11,12]},titles:["Explanation","Asynchronous Programming 101","Engine and Connection","SQLAlchemy 2.0","Why Asynchronous ORM?","How-to Guides","Use Alembic","Bake Queries","Contributing","CRUD","Frequently Asked Questions","JSON Property","Loaders and Relationship","Connection Pool","Schema Declaration","Transaction","Welcome to GINO\u2019s documentation!","Reference","API Reference","gino package","gino.aiocontextvars module","gino.api module","gino.bakery module","gino.crud module","gino.declarative module","gino.dialects package","gino.dialects.aiomysql module","gino.dialects.asyncpg module","gino.dialects.base module","gino.engine module","gino.exceptions module","gino.ext package","gino.json_support module","gino.loader module","gino.schema module","gino.strategies module","gino.transaction module","Extensions","Sanic Support","Starlette Support","Tornado Support","History","Tutorials","\u5b98\u5ba3\uff1aPython \u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668","Build a FastAPI Server","GINO Basics"],titleterms:{"101":1,"2017":41,"2018":41,"2019":41,"2020":41,"\u4f18\u52bf\u4e0e\u4e0d\u8db3":43,"\u5148\u8bf4":43,"\u5173\u4e8e\u4f5c\u8005":43,"\u518d\u8bf4":43,"\u53c2\u8003\u6587\u732e":43,"\u5b98\u5ba3":43,"\u5efa\u8bbe\u793e\u4f1a\u4e3b\u4e49":43,"\u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668":43,"\u65b9\u4fbf\u5feb\u6377":43,"\u662f\u8c01":43,"\u7b80\u5355\u660e\u4e86":43,"new":44,One:12,The:[1,3],Use:[6,7],Useful:16,access:4,add:44,admin:10,advanc:12,aiocontextvar:[10,20],aiomysql:26,alemb:[6,10,44],api:[3,7,18,21,41,44],appli:6,ask:10,async:3,asynchron:[1,4],asyncio:[4,10],asyncpg:27,auto:3,autocommit:3,avail:7,bake:7,bakedqueri:7,bakeri:[7,22],bare:7,base:28,basic:[15,45],batch:10,bug:8,build:44,bulk:10,can:10,column:10,commit:3,complex:10,complic:3,con:1,configur:39,connect:[2,10,13,39,45],content:[19,25,31],contextvar:41,contribut:8,control:15,cooper:1,core:14,creat:[2,6,11,44,45],crud:[9,23,45],current_connect:2,custom:7,databas:[4,10],declar:[14,24,45],defin:10,delet:45,depend:44,develop:41,dialect:[25,26,27,28],differ:10,django:10,document:[8,16],doe:10,don:[4,7],done:4,earli:41,engin:[2,7,10,14,29,41],except:30,execut:[2,10],exist:10,explan:0,explicit:4,express:12,ext:31,extens:[37,44],fastapi:44,featur:[8,10],feedback:8,first:6,fix:8,fly:10,frequent:10,get:[8,45],gino:[7,10,14,16,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,41,43,44,45],ginoconnect:41,guid:5,guidelin:8,help:4,histori:41,hook:11,how:[4,5,7,10],implement:8,implicit:2,index:[10,11],initi:10,insert:10,instal:45,integr:[7,44],interfac:10,introduct:45,isol:3,join:10,json:11,json_support:32,lazi:[2,39],level:3,link:16,load:10,loader:[7,12,33],local:41,manag:2,mani:12,manual:15,migrat:[6,41],model:[12,44,45],modul:[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],multipl:10,multitask:1,nest:15,none_as_non:41,note:44,oper:45,orm:[4,10,14],other:12,packag:[19,25,31],paramet:10,pend:41,pool:13,prepar:7,print:10,pro:1,product:[4,44],program:1,project:44,properti:11,pull:8,python:43,queri:[2,7,10,41],question:10,quick:11,raw:10,refer:[17,18],referenc:12,relationship:[10,12],releas:41,report:8,request:8,result:10,retriev:45,reus:2,reusabl:2,revis:6,right:4,run:10,same:10,sanic:38,schema:[14,34],self:12,server:44,set:6,simpl:44,solut:3,sql:10,sqlalchemi:[3,10],ssl:10,starlett:39,start:[8,11,44],starv:4,statement:7,stori:1,strategi:35,submit:8,submodul:[19,25],subpackag:19,support:[10,38,39,40],tabl:10,task:41,test:44,through:10,tip:8,tornado:40,transact:[15,36,41],tutori:42,twice:10,type:8,updat:[10,45],usag:[12,15],use:10,user:10,want:7,welcom:16,what:[7,10],when:4,why:4,work:[10,38,39],write:[8,44],xxxx:10}}) \ No newline at end of file diff --git a/docs/en/1.1b2/tutorials.html b/docs/en/1.1b2/tutorials.html new file mode 100644 index 0000000..1771be3 --- /dev/null +++ b/docs/en/1.1b2/tutorials.html @@ -0,0 +1,235 @@ + + + + + + + + Tutorials - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/tutorials/announcement.html b/docs/en/1.1b2/tutorials/announcement.html new file mode 100644 index 0000000..4a75d3f --- /dev/null +++ b/docs/en/1.1b2/tutorials/announcement.html @@ -0,0 +1,491 @@ + + + + + + + + 官宣:Python 异步编程再添一利器 - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    官宣:Python 异步编程再添一利器

    +
    +

    GINO 填补了国内外 asyncio ORM 领域的空白

    +
    +

    随着 Tornado1 和 asyncio2 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +GINO 了解一下!

    +../_images/python-gino.webp +
    +

    GINO 是谁

    +

    GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义3手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,你肯定要问一句“这是谁”。

    +

    ORM,即关系对象映射(Object-Relational Mapping4),是一类开发人员喜闻乐见的效率工具,它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢?

    +

    因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 current_user.name +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试?

    +

    传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,但应用到大型项目中却十分考验开发人员的平均水平。

    +

    所有这些问题如果再放进异步编程的环境里,那就是 O(n2) 的复杂度了——哦不,是 +O(2n)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。

    +

    所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,我索性在 1.0 稳定版发布的前夕做个年终总结。

    +
    +
    +

    先说“方便快捷”

    +
    +

    “方便快捷”主要说的是开发效率。

    +
    +

    重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。

    +
    from gino import Gino
    +db = Gino()
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +
    +

    这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟?

    +

    没有错,这就是 SQLAlchemy ORM5 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core6(SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic7、各种 SQLAlchemy 的增强插件8、专业领域的 PostGIS/geoalchemy9等,GINO 全都兼容。

    +

    是不是十分方便、十分快捷?不止这样。

    +

    GINO 一站式地解决了常用 CRUD 快捷方式10、上下文管理(aiocontextvars1112)、 +数据库事务封装和嵌套13、连接池管理和懒加载14等多项便捷功能,无额外依赖关系,即装即用。

    +
    daisy = await User.create(name="daisy")
    +await daisy.update(name="Daisy").apply()
    +
    +
    +

    GINO 还提供了15各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado1、aiohttp16、Sanic17、FastAPI18/Starlette19、Quart20什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。

    +

    为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa21 的降维打击:

    +
      +
    1. 最少侵入型22:SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。

    2. +
    3. 终身不婚型23:天生厌恶“对象”,只愿定义“表”,空手接 SQL。

    4. +
    5. 火力全开型24:最大程度的便利,非典型异步 ORM。

    6. +
    +

    最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、一秒可读百万行的 asyncpg25,以及 uvloop26(可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,深受俄罗斯和乌克兰人民的爱戴。

    +
    +
    +

    再说“简单明了”

    +
    +

    Explicit is better than implicit.

    +

    Simple is better than complex.

    +

    –The Zen of Python, PEP 2027

    +
    +

    Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,因此 GINO 的很多设计都受到了明确性的影响。

    +

    比如说,GINO 的 Model 是完全无状态的普通 Python 对象(POPO28—— 例如前面的 User +类,它的实例 daisy 就是内存里面的常规对象,你可以用 daisy.name 访问属性,也可以用 +daisy.name = "DAISY" 来修改属性,或者用 u = User() 来创建新的实例,这些操作都不会访问数据库,绝对绿色环保无毒副作用。

    +

    等到需要操作数据库的时候,你一定会有感知的。比如执行 INSERT 要用 +u = await User.create(),而 UPDATE 则是 +await u.update(name="Daisy").apply()

    +
    +

    Hint

    +

    其中,u.update(name="Daisy") 与 u.name = "Daisy" 类似,都是只在内存里修改对象的属性,不同的是 u.update() 还会返回一个包含本次变更的中间结果,对其执行 await xxx.apply() 则会将这些变更应用到数据库里。

    +
    +

    这里的 await 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 await +就没有数据库操作。

    +

    另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders29。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 u = await User.get(1) 可以直接获取到 ID 为 1 的用户对象。但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。加载器的用法也是很简单的,比如一个用户可能写了很多本书:

    +
    class Book(db.Model):
    +    __tablename__ = "books"
    +    id = db.Column(db.Integer, primary_key=True)
    +    title = db.Column(db.String)
    +    author_id = db.ForeignKey("users.id")
    +
    +
    +

    然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者:

    +
    query = Book.outerjoin(User).select()
    +loader = Book.load(author=User)
    +async for book in query.gino.load(loader).iterate():
    +    print(book.title, "written by", book.author.name)
    +
    +
    +

    很简单的一个外连接查询 Book.outerjoin(User),配合一个直观的加载器 +Book.load(author=User),就实现了:

    +
      +
    1. 执行 SELECT * FROM books LEFT JOIN users ON ...

    2. +
    3. 将数据库返回结果的每一行中,属于 books 的字段加载成一个 Book 实例;

    4. +
    5. 然后将该行中剩下的属于 users 的字段加载成一个 User 实例;

    6. +
    7. 最后将 User 实例设置到 Book 实例的 author 属性上。

    8. +
    +

    既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。

    +
    +
    +

    优势与不足

    +

    随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM30、ORM31(是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。

    +

    GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,可以谨慎地用于生产环境了。

    +

    以下是近来统计到的关于 GINO 的应用案例:

    + +

    另外,GINO 还贴心地提供了中文文档32,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!):

    +../_images/docs.webp +

    GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。

    +
    +
    +

    建设社会主义

    +

    GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 4888 元的 PyCharm +专业版全家桶 License 一枚33(没有发票)。

    +

    目前急需帮助的有:

    +
      +
    1. 各个 Web 框架插件的维护工作需要多人认领;

    2. +
    3. 更多的例子和文档,以及中文、俄文的翻译;

    4. +
    5. MySQL 的支持。

    6. +
    +

    以及下面这些一直需要的帮助:

    +
      +
    1. 用 GINO,找 bug,提建议;

    2. +
    3. 修 bug,做功能,提 PR;

    4. +
    5. 维护社区,回答问题,参与讨论;

    6. +
    7. 最后也是最重要的:去 GitHub 上给 GINO 加一颗星星!

    8. +
    +../_images/happy-hacking.png +
    +
    +

    关于作者

    +

    87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P

    +

    特别感谢 Tony Wang 对 GINO 项目的贡献,以及所有人的贡献。

    +
    +
    +

    参考文献

    +
    +
    1(1,2)
    +

    Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado.

    +
    +
    2
    +

    Python Software Foundation. asyncio — 异步 I/O. +https://docs.python.org/zh-cn/3/library/asyncio.html.

    +
    +
    3
    +

    维基百科. GNU. https://zh.wikipedia.org/wiki/GNU.

    +
    +
    4
    +

    维基百科. 对象关系映射. +https://zh.wikipedia.org/wiki/对象关系映射.

    +
    +
    5
    +

    维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy.

    +
    +
    6
    +

    Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. +https://docs.sqlalchemy.org/en/13/core/index.html.

    +
    +
    7
    +

    Michael Bayer. Welcome to Alembic’s documentation!. +https://alembic.sqlalchemy.org/en/latest/.

    +
    +
    8
    +

    Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. +https://github.com/dahlia/awesome-sqlalchemy.

    +
    +
    9
    +

    Ricardo GarciaSilva. Does it have postgis support?. +https://github.com/python-gino/gino/issues/627.

    +
    +
    10
    +

    Fantix King. GINO 基础教程 - 增删改查. +https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations.

    +
    +
    11
    +

    Python Software Foundation. contextvars —Context Variables. +https://docs.python.org/3/library/contextvars.html.

    +
    +
    12
    +

    Fantix King. gino/aiocontextvars.py. +https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py.

    +
    +
    13
    +

    Fantix King. 数据库事务. +https://python-gino.org/docs/zh/master/how-to/transaction.html.

    +
    +
    14
    +

    Fantix King. 引擎与连接. +https://python-gino.org/docs/zh/master/explanation/engine.html.

    +
    +
    15
    +

    GINO Community. GINO Community. https://github.com/python-gino/.

    +
    +
    16
    +

    aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/.

    +
    +
    17
    +

    Sanic Community. SanicFramework. https://sanicframework.org/.

    +
    +
    18
    +

    Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/.

    +
    +
    19
    +

    Encode OSS. Starlette. https://www.starlette.io/.

    +
    +
    20
    +

    Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/.

    +
    +
    21
    +

    Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. +https://github.com/CanopyTax/asyncpgsa.

    +
    +
    22
    +

    Fantix King. 表结构定义 -GINO 引擎. +https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine.

    +
    +
    23
    +

    Fantix King. 表结构定义 - GINO core. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core.

    +
    +
    24
    +

    Fantix King. 表结构定义 - GINO ORM. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm.

    +
    +
    25
    +

    MagicStack Inc. +asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. +https://github.com/MagicStack/asyncpg.

    +
    +
    26
    +

    MagicStack Inc. uvloop -Ultra fast asyncio event loop. +https://github.com/MagicStack/uvloop.

    +
    +
    27
    +

    Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/.

    +
    +
    28
    +

    Wikipedia. Plain old Java object. +https://en.wikipedia.org/wiki/Plain_old_Java_object.

    +
    +
    29
    +

    Fantix King. 加载器与关系. +https://python-gino.org/docs/zh/master/how-to/loaders.html.

    +
    +
    30
    +

    Andrey Bondar. Tortoise ORM. https://tortoise.github.io/.

    +
    +
    31
    +

    Encode OSS. ORM - An async ORM. https://github.com/encode/orm.

    +
    +
    32
    +

    Fantix King. 欢迎来到 GINO 的文档!. +https://python-gino.org/docs/zh/master/index.html.

    +
    +
    33
    +

    JetBrains s.r.o. Open Source Licenses. +https://www.jetbrains.com/community/opensource/#support.

    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/tutorials/fastapi.html b/docs/en/1.1b2/tutorials/fastapi.html new file mode 100644 index 0000000..9a320ff --- /dev/null +++ b/docs/en/1.1b2/tutorials/fastapi.html @@ -0,0 +1,737 @@ + + + + + + + + Build a FastAPI Server - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Build a FastAPI Server

    +

    In this tutorial, we’ll build a production-ready FastAPI server together. +The full functional example is available here.

    +

    Our application stack will look like this:

    +../_images/gino-fastapi.svg
    +

    Start a New Project

    +

    Instead of pip, let’s use the shiny Poetry to manage our project. Follow the link to +install Poetry, and create our new +project in an empty directory:

    +
    $ mkdir gino-fastapi-demo
    +$ cd gino-fastapi-demo
    +$ git init
    +$ poetry init
    +
    +
    +

    Then follow the Poetry guide to finish the initialization - you may say “no” to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains gino-fastapi-demo.

    +
    +
    +

    Add Dependencies

    +

    FastAPI is built on top of the Starlette framework, so we shall use the GINO +extension for Starlette. Simply run:

    +
    $ poetry add gino[starlette]
    +
    +
    +

    Then let’s add FastAPI, together with the lightning-fast ASGI server Uvicorn, and +Gunicorn as a production application server:

    +
    $ poetry add fastapi uvicorn gunicorn
    +
    +
    +

    For database migration, we’ll use Alembic. Because it uses normal DB-API, we need +psycopg here too:

    +
    $ poetry add alembic psycopg2
    +
    +
    +

    At last, let’s add pytest in the development environment for testing. We also want to +add the requests library to use the Starlette TestClient:

    +
    $ poetry add -D pytest requests
    +
    +
    +
    +

    Hint

    +

    With the steps above, Poetry will automatically create a virtualenv for you +behind the scene, and all the dependencies are installed there. We will assume +using this for the rest of the tutorial. But you’re free to create your own +virtualenv, and Poetry will honor it when it’s activated.

    +
    +

    That’s all, this is my pyproject.toml created by Poetry, yours should look similar:

    +
    [tool.poetry]
    +name = "gino-fastapi-demo"
    +version = "0.1.0"
    +description = ""
    +authors = ["Fantix King <fantix.king@gmail.com>"]
    +
    +[tool.poetry.dependencies]
    +python = "^3.8"
    +gino = {version = "^1.0", extras = ["starlette"]}
    +fastapi = "^0.54.1"
    +uvicorn = "^0.11.3"
    +gunicorn = "^20.0.4"
    +alembic = "^1.4.2"
    +psycopg2 = "^2.8.5"
    +
    +[tool.poetry.dev-dependencies]
    +pytest = "^5.4.1"
    +requests = "^2.23.0"
    +
    +[build-system]
    +requires = ["poetry>=0.12"]
    +build-backend = "poetry.masonry.api"
    +
    +
    +../_images/gino-fastapi-poetry.svg

    And there’s also an auto-generated poetry.lock file with the frozen versions. The +directory layout should look like the diagram on the right. Now let’s add the two files +to the Git repository (we will skip showing these git operations in future steps):

    +
    $ git add pyproject.toml poetry.lock
    +$ git commit -m 'add project dependencies'
    +
    +
    +
    +
    +

    Write a Simple Server

    +

    Now let’s write some Python code.

    +

    We’ll create an extra src directory to include all the Python files, as demonstrated +in the diagram below. This is known as the “src layout” providing a cleaner hierarchy.

    +../_images/gino-fastapi-src.svg

    The root Python package of our project is named as gino_fastapi_demo, under which we +will create two Python modules:

    +
      +
    • asgi as the ASGI entry point - we’ll feed it to the ASGI server

    • +
    • main to initialize our server

    • +
    +

    Here’s main.py:

    +
    from fastapi import FastAPI
    +
    +def get_app():
    +    app = FastAPI(title="GINO FastAPI Demo")
    +    return app
    +
    +
    +

    And we’ll simply instantiate our application in asgi.py:

    +
    from .main import get_app
    +
    +app = get_app()
    +
    +
    +

    Then run poetry install to link our Python package into the PYTHONPATH in +development mode. We’ll be able to start a Uvicorn development server after that:

    +
    $ poetry install
    +Installing dependencies from lock file
    +
    +No dependencies to install or update
    +
    +  - Installing gino-fastapi-demo (0.1.0)
    +
    +$ poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    +INFO:     Started reloader process [53010]
    +INFO:     Started server process [53015]
    +INFO:     Waiting for application startup.
    +INFO:     Application startup complete.
    +
    +
    +

    The --reload option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server.

    +
    +

    Hint

    +

    As mentioned previously, if you’re in your own virtualenv, the command poetry run +uvicorn can be simplified as just uvicorn.

    +

    poetry run is a convenient shortcut to run the following command in the +virtualenv managed by Poetry.

    +
    +
    +
    +

    Add GINO Extension

    +../_images/gino-fastapi-config.svg

    Now let’s add GINO to our server.

    +

    First of all, we need a way to configure the database. In this tutorial, we’ll use the +configuration system from Starlette. +Add src/gino_fastapi_demo/config.py as follows:

    +
    from sqlalchemy.engine.url import URL, make_url
    +from starlette.config import Config
    +from starlette.datastructures import Secret
    +
    +config = Config(".env")
    +
    +DB_DRIVER = config("DB_DRIVER", default="postgresql")
    +DB_HOST = config("DB_HOST", default=None)
    +DB_PORT = config("DB_PORT", cast=int, default=None)
    +DB_USER = config("DB_USER", default=None)
    +DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    +DB_DATABASE = config("DB_DATABASE", default=None)
    +DB_DSN = config(
    +    "DB_DSN",
    +    cast=make_url,
    +    default=URL(
    +        drivername=DB_DRIVER,
    +        username=DB_USER,
    +        password=DB_PASSWORD,
    +        host=DB_HOST,
    +        port=DB_PORT,
    +        database=DB_DATABASE,
    +    ),
    +)
    +DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1)
    +DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16)
    +DB_ECHO = config("DB_ECHO", cast=bool, default=False)
    +DB_SSL = config("DB_SSL", default=None)
    +DB_USE_CONNECTION_FOR_REQUEST = config(
    +    "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True
    +)
    +DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1)
    +DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1)
    +
    +
    +

    This config file will load from environment variable first, if not found then from a +file named .env from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this:

    +
    $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +
    +
    +../_images/gino-fastapi-env.svg

    Or set them in the file .env (this file must not be committed into Git, remember to +add it to .gitignore):

    +
    DB_HOST=localhost
    +DB_USER=postgres
    +
    +
    +

    Now it’s time to create a PostgreSQL database and set the connection variables +correctly here. This is usually something like createdb yourdbname, but it may vary +across different platforms, so we won’t cover this part in this tutorial.

    +
    +

    Tip

    +

    Alternatively, you could also set DB_DSN to for example +postgresql://user:password@localhost:5432/dbname to override the other individual +config values like DB_HOST defined before DB_DSN.

    +

    If defined, DB_DSN always have the higher priority over the individual ones, +regardless of where they are defined - even if DB_HOST is defined in environment +variable and DB_DSN is defined in .env file, DB_HOST is still ignored. +Default value doesn’t count.

    +
    +../_images/gino-fastapi-models.svg

    Then, create a new Python sub-package gino_fastapi_demo.models to encapsulate +database-related code, and add the code below to +src/gino_fastapi_demo/models/__init__.py:

    +
    from gino.ext.starlette import Gino
    +
    +from .. import config
    +
    +db = Gino(
    +    dsn=config.DB_DSN,
    +    pool_min_size=config.DB_POOL_MIN_SIZE,
    +    pool_max_size=config.DB_POOL_MAX_SIZE,
    +    echo=config.DB_ECHO,
    +    ssl=config.DB_SSL,
    +    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    +    retry_limit=config.DB_RETRY_LIMIT,
    +    retry_interval=config.DB_RETRY_INTERVAL,
    +)
    +
    +
    +

    At last, modify src/gino_fastapi_demo/main.py to install the GINO extension:

    +
     from fastapi import FastAPI
    ++
    ++from .models import db
    +
    + def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    ++    db.init_app(app)
    +     return app
    +
    +
    +

    Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database:

    +
    WARNING:  Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading...
    +INFO:     Shutting down
    +INFO:     Waiting for application shutdown.
    +INFO:     Application shutdown complete.
    +INFO:     Finished server process [63562]
    +INFO:     Started server process [63563]
    +INFO:     Waiting for application startup.
    +INFO:     Connecting to the database: postgresql://fantix:***@localhost
    +INFO:     Database connection pool created: <asyncpg.pool.Pool max=16 min=1 cur=1 use=0>
    +INFO:     Application startup complete.
    +
    +
    +
    +
    +

    Create Models and API

    +../_images/gino-fastapi-models-users.svg

    It’s time to implement the API now. Let’s say we are building a user management service, +through which we could add users, list users and delete users.

    +

    First of all, we need a database table users to store the data, mapped to a GINO +model named User. We shall add the model in gino_fastapi_demo.models.users:

    +
    from . import db
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default="unnamed")
    +
    +
    +../_images/gino-fastapi-views.svg

    The model definition is simple enough to explain itself.

    +

    Then we only have to use it properly in the API implementation, for which we’ll create a +new Python sub-package gino_fastapi_demo.views, and a new module +gino_fastapi_demo.views.users as follows:

    +
    from fastapi import APIRouter
    +from pydantic import BaseModel
    +
    +from ..models.users import User
    +
    +router = APIRouter()
    +
    +@router.get("/users/{uid}")
    +async def get_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    return user.to_dict()
    +
    +class UserModel(BaseModel):
    +    name: str
    +
    +@router.post("/users")
    +async def add_user(user: UserModel):
    +    rv = await User.create(nickname=user.name)
    +    return rv.to_dict()
    +
    +@router.delete("/users/{uid}")
    +async def delete_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    await user.delete()
    +    return dict(id=uid)
    +
    +def init_app(app):
    +    app.include_router(router)
    +
    +
    +

    The APIRouter holds our new APIs locally, and init_app is used to integrate it +into our FastAPI application. Here we want some inversion of control: let’s make the +APIs plugable, so that we don’t have to import all possible future views manually. We +shall use the Entry Points feature to load the dependencies. Add this code below to +gino_fastapi_demo.main:

    +
    import logging
    +from importlib.metadata import entry_points
    +
    +logger = logging.getLogger(__name__)
    +
    +def load_modules(app=None):
    +    for ep in entry_points()["gino_fastapi_demo.modules"]:
    +        logger.info("Loading module: %s", ep.name)
    +        mod = ep.load()
    +        if app:
    +            init_app = getattr(mod, "init_app", None)
    +            if init_app:
    +                init_app(app)
    +
    +
    +
    +

    Hint

    +

    If you’re running Python < 3.8, you’ll need this importlib-metadata backport.

    +
    +

    And call it in our application factory:

    +
     def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    +     db.init_app(app)
    ++    load_modules(app)
    +     return app
    +
    +
    +

    Finally, define the entry points in pyproject.toml following the Poetry document +for plugins:

    +
    [tool.poetry.plugins."gino_fastapi_demo.modules"]
    +"users" = "gino_fastapi_demo.views.users"
    +
    +
    +

    Run poetry install again to activate the entry points - you may need to restart the +Uvicorn development server manually, as the reloader cannot capture the changes we made +to pyproject.toml.

    +

    Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven’t created the database tables.

    +
    +
    +

    Integrate with Alembic

    +

    To get started with Alembic, run this command in the project root directory:

    +
    $ poetry run alembic init migrations
    +
    +
    +../_images/gino-fastapi-alembic.svg

    This will generate a new directory migrations where Alembic will store database +migration revisions. At the same time, an alembic.ini file is created in the project +root directory. Let’s simply add all of them to Git control.

    +

    For Alembic to use our data models defined with GINO (and of course the database +config), we need to modify migrations/env.py to connect with the GINO instance:

    +
     # add your model's MetaData object here
    + # for 'autogenerate' support
    + # from myapp import mymodel
    + # target_metadata = mymodel.Base.metadata
    +-target_metadata = None
    ++from gino_fastapi_demo.config import DB_DSN
    ++from gino_fastapi_demo.main import db, load_modules
    ++
    ++load_modules()
    ++config.set_main_option("sqlalchemy.url", str(DB_DSN))
    ++target_metadata = db
    +
    +
    +

    Then create our first migration revision with:

    +
    $ poetry run alembic revision --autogenerate -m 'add users table'
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.autogenerate.compare] Detected added table 'users'
    +  Generating migrations/versions/32c0feba61ea_add_users_table.py ...  done
    +
    +
    +

    The generated revision file should roughly look like this:

    +
    def upgrade():
    +    op.create_table(
    +        "users",
    +        sa.Column("id", sa.BigInteger(), nullable=False),
    +        sa.Column("nickname", sa.Unicode(), nullable=True),
    +        sa.PrimaryKeyConstraint("id"),
    +    )
    +
    +def downgrade():
    +    op.drop_table("users")
    +
    +
    +
    +

    Hint

    +

    Whenever there is a change to the database schema in the future, just modify the +GINO models and run alembic revision --autogenerate again to generate new +revisions to track the change. Remember to review the revision file - you may want +to adjust it.

    +
    +

    Eventually, let’s apply this migration, by upgrading to the latest revision:

    +
    $ poetry run alembic upgrade head
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.runtime.migration] Running upgrade  -> 32c0feba61ea, add users table
    +
    +
    +

    Now all the APIs should be fully operational, try with the Swagger UI.

    +
    +
    +

    Write the Tests

    +

    In order not to break our development database with running tests, let’s create a +separate database to run tests. Apply this change to gino_fastapi_demo.config:

    +
     config = Config(".env")
    +
    ++TESTING = config("TESTING", cast=bool, default=False)
    +
    + DB_DRIVER = config("DB_DRIVER", default="postgresql")
    + DB_HOST = config("DB_HOST", default=None)
    + DB_PORT = config("DB_PORT", cast=int, default=None)
    + DB_USER = config("DB_USER", default=None)
    + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    + DB_DATABASE = config("DB_DATABASE", default=None)
    ++if TESTING:
    ++    if DB_DATABASE:
    ++        DB_DATABASE += "_test"
    ++    else:
    ++        DB_DATABASE = "gino_fastapi_demo_test"
    + DB_DSN = config(
    +
    +
    +
    +

    Hint

    +

    You need to run createdb to actually create the database. If you have set +DB_DATABASE in .env - e.g. DB_DATABASE=mydb, the name of the testing +database should be mydb_test. Or else, gino_fastapi_demo_test.

    +
    +

    Then, let’s create our pytest fixture in tests/conftest.py:

    +
    import pytest
    +from alembic.config import main
    +from starlette.config import environ
    +from starlette.testclient import TestClient
    +
    +environ["TESTING"] = "TRUE"
    +
    +@pytest.fixture
    +def client():
    +    from gino_fastapi_demo.main import db, get_app
    +
    +    main(["--raiseerr", "upgrade", "head"])
    +
    +    with TestClient(get_app()) as client:
    +        yield client
    +
    +    main(["--raiseerr", "downgrade", "base"])
    +
    +
    +../_images/gino-fastapi-tests.svg

    This fixture creates all the database tables before running the test, yield a Starlette +TestClient, and drop all the tables with all the data after the test to maintain a +clean environment for the next test.

    +

    Here’s a sample test in tests/test_users.py:

    +
    import uuid
    +
    +def test_crud(client):
    +    # create
    +    nickname = str(uuid.uuid4())
    +    r = client.post("/users", json=dict(name=nickname))
    +    r.raise_for_status()
    +
    +    # retrieve
    +    url = f"/users/{r.json()['id']}"
    +    assert client.get(url).json()["nickname"] == nickname
    +
    +    # delete
    +    client.delete(url).raise_for_status()
    +    assert client.get(url).status_code == 404
    +
    +
    +

    Then run the test:

    +
    $ poetry run pytest
    +=========================== test session starts ===========================
    +platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    +rootdir: gino-fastapi-demo
    +collected 1 item
    +
    +tests/test_users.py .                                               [100%]
    +
    +============================ 1 passed in 1.21s ============================
    +
    +
    +
    +
    +

    Notes for Production

    +

    Given the popularity of Docker/Kubernetes, we’ll build a Dockerfile for our demo:

    +
    FROM python:3.8-alpine as base
    +
    +FROM base as builder
    +RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev
    +RUN pip install poetry
    +COPY . /src/
    +WORKDIR /src
    +RUN python -m venv /env && . /env/bin/activate && poetry install
    +
    +FROM base
    +RUN apk add --no-cache postgresql-libs
    +COPY --from=builder /env /env
    +COPY --from=builder /src /src
    +WORKDIR /src
    +CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"]
    +
    +
    +

    In this Dockerfile, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn with +UvicornWorker from Uvicorn as the worker class for best production reliability.

    +

    Let’s review what we have in the project.

    +../_images/gino-fastapi-layout.svg

    This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live:

    +
      +
    • Set DB_RETRY_LIMIT to a larger number to allow staring the application server +before the database is fully ready.

    • +
    • Implement the same retry logic in migrations/env.py so that Alembic gets the same +functionality.

    • +
    • Enable DB_SSL if needed.

    • +
    • Write a docker-compose.yml for other developers to get a quick taste or even use +it for development.

    • +
    • Enable CI, install pytest-cov and use --cov-fail-under to guarantee coverage.

    • +
    • Integrate static code analysis tools and security/CVE checking tools.

    • +
    • Automate Alembic upgrade properly - e.g. after new version is deployed.

    • +
    • Be aware of the common security attacks like CSRF, XSS, etc.

    • +
    • Write load tests.

    • +
    +

    Again, the source code of the demo is available here, +and the source of this tutorial is here. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking!

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/1.1b2/tutorials/tutorial.html b/docs/en/1.1b2/tutorials/tutorial.html new file mode 100644 index 0000000..8dd7590 --- /dev/null +++ b/docs/en/1.1b2/tutorials/tutorial.html @@ -0,0 +1,600 @@ + + + + + + + + GINO Basics - GINO 1.1.0b2 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    GINO Basics

    +

    This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of:

    + +

    Knowledge of SQLAlchemy is not required.

    +
    +

    Introduction

    +

    Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API.

    +

    You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won’t make your +code run faster, if not slower. Please read Why Asynchronous ORM? for more +information.

    +
    +
    +

    Installation

    +

    To install GINO, run this command in your terminal:

    +
    $ pip install gino
    +
    +
    +

    This is the preferred method to install GINO, as it will always install the +most recent stable release.

    +

    If you don’t have pip installed, this Python installation guide can guide +you through the process.

    +

    Alternatively, if you are using Poetry to manage your project dependencies, +you may want to run:

    +
    $ poetry add gino
    +
    +
    +
    +
    +

    Declare Models

    +

    First of all, we’ll need a Gino object, usually under the +name of db as a global variable:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +

    db acts like a reference to the database, most database interactions will +go through it.

    +

    “Model” is a basic concept in GINO, it is a Python class inherited from +db.Model. Each Model +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let’s declare a model:

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +

    By declaring this User class, we are actually defining a database table +named users, with two columns id and nickname. Note that the fixed +__tablename__ property is required. GINO +suggests singular for model names, and plural for table names. Each +db.Column property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to db types here in the SQLAlchemy +documentation.

    +
    +

    Note

    +

    SQLAlchemy is a powerful ORM library for non-asynchronous programming in +Python, on top of which GINO is built. SQLAlchemy supports many popular +RDBMS including PostgreSQL and MySQL through different dialect +implementation, so that the same Python code can be compiled into different +SQL depending on the dialect you choose. GINO inherited this support too, +but for now there is only one dialect for PostgreSQL through asyncpg.

    +
    +

    If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:

    +
    class Booking(db.Model):
    +    __tablename__ = 'bookings'
    +
    +   day = db.Column(db.Date)
    +   booker = db.Column(db.String)
    +   room = db.Column(db.String)
    +
    +   _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey')
    +   _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True)
    +   _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room')
    +
    +
    +

    It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +here in the +SQLAlchemy documentation.

    +

    Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the __table_args__ attribute. In order +to e.g. define constraints in mixin classes, +declared_attr() is required. Please feel free to read +more about it in its API documentation.

    +
    +
    +

    Get Connected

    +

    The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let’s create a +PostgreSQL database for this tutorial:

    +
    $ createdb gino
    +
    +
    +

    Then we tell our db object to connect to this database:

    +
    import asyncio
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +

    If this runs successfully, then you are connected to the newly created database. +Here postgresql indicates the database dialect to use (the default driver +is asyncpg, you can explicitly specify that with postgresql+asyncpg://, +or simply asyncpg://), localhost is where the server is, and gino +is the name of the database. Check here for more +information about how to compose this database URL.

    +
    +

    Note

    +

    Under the hood set_bind() calls +create_engine() and bind the engine to this db object. GINO +engine is similar to SQLAlchemy engine, but not identical. Because GINO +engine is asynchronous, while the other is not. Please refer to the API +reference of GINO for more information.

    +
    +

    Now that we are connected, let’s create the table in database (in the same +main() method):

    +
    await db.gino.create_all()
    +
    +
    +
    +

    Warning

    +

    It is db.gino.create_all, +not db.create_all, because +db is inherited from SQLAlchemy MetaData, +and db.create_all is from +SQLAlchemy using non-asynchronous methods, which doesn’t work with the +bound GINO engine.

    +

    In practice create_all() is usually +not an ideal solution. To manage database schema, tool like Alembic is +recommended, please see how to Use Alembic.

    +
    +

    If you want to explicitly disconnect from the database, you can do this:

    +
    await db.pop_bind().close()
    +
    +
    +

    Let’s review the code we have so far together in one piece before moving on:

    +
    import asyncio
    +from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +    await db.gino.create_all()
    +
    +    # further code goes here
    +
    +    await db.pop_bind().close()
    +
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +
    +
    +

    CRUD Operations

    +

    In order to operate on the database, one of GINO’s core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations.

    +
    +

    Create

    +

    Let’s start by creating a User:

    +
    user = await User.create(nickname='fantix')
    +# This will cause GINO to execute this SQL with parameter 'fantix':
    +# INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname
    +
    +
    +

    As mentioned previously, user object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:

    +
    print(f'ID:       {user.id}')           # 1
    +print(f'Nickname: {user.nickname}')     # fantix
    +
    +
    +

    It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:

    +
    user = User(nickname='fantix')
    +user.nickname += ' (founder)'
    +await user.create()
    +
    +
    +
    +
    +

    Retrieve

    +

    To retrieve a model object from database by primary key, you can use the class +method get() on the model class. Now let’s retrieve +the same row:

    +
    user = await User.get(1)
    +# SQL (parameter: 1):
    +# SELECT users.id, users.nickname FROM users WHERE users.id = $1
    +
    +
    +

    Normal SQL queries are done through a class property +query. For example, let’s retrieve all User +objects from database as a list:

    +
    all_users = await db.all(User.query)
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +

    Alternatively, you can use the gino extension on +query. This has exactly the same effect as above:

    +
    all_users = await User.query.gino.all()
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +
    +

    Note

    +

    User.query is actually a SQLAlchemy query, with its own +non-asynchronous execution methods. GINO added this gino extension on +all executable SQLAlchemy clause objects to conveniently execute them in +the asynchronous way, so that it is even not needed to import the db +reference for execution.

    +
    +

    Now let’s add some filters. For example, find all users with ID lower than 10:

    +
    founding_users = await User.query.where(User.id < 10).gino.all()
    +# SQL (parameter: 10):
    +# SELECT users.id, users.nickname FROM users WHERE users.id < $1
    +
    +
    +

    Read more here +about writing queries, because the query object is exactly from SQLAlchemy core.

    +
    +

    Warning

    +

    Once you get a model object, it is purely in memory and fully detached from +the database. That means, if the row is externally updated, the object +values remain unchanged. Likewise, changes made to the object won’t affect +the database values.

    +

    Also, GINO keeps no track of model objects, therefore getting the same row +twice returns two different object with identical values. Modifying one +does not magically affect the other one.

    +

    Different than traditional ORMs, the GINO model objects are more like +objective SQL results, rather than stateful ORM objects. In order to adapt +for asynchronous programming, GINO is designed to be that simple. That’s +also why GINO Is Not ORM.

    +
    +

    Sometimes we want to get only one object, for example getting the user by name +when logging in. There’s a shortcut for this scenario:

    +
    user = await User.query.where(User.nickname == 'fantix').gino.first()
    +# SQL (parameter: 'fantix'):
    +# SELECT users.id, users.nickname FROM users WHERE users.nickname = $1
    +
    +
    +

    If there is no user named “fantix” in database, user will be None.

    +

    And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +select() class method:

    +
    name = await User.select('nickname').where(User.id == 1).gino.scalar()
    +# SQL (parameter: 1):
    +# SELECT users.nickname FROM users WHERE users.id = $1
    +print(name)  # fantix
    +
    +
    +

    Or get the count of all users:

    +
    population = await db.func.count(User.id).gino.scalar()
    +# SQL:
    +# SELECT count(users.id) AS count_1 FROM users
    +print(population)  # 17 for example
    +
    +
    +
    +
    +

    Update

    +

    Then let’s try to make some modifications. In this example we’ll mixin some +retrieve operations we just tried.

    +
    # create a new user
    +user = await User.create(nickname='fantix')
    +
    +# get its name
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +assert name == user.nickname  # they are both 'fantix' before the update
    +
    +# modification here
    +await user.update(nickname='daisy').apply()
    +# SQL (parameters: 'daisy', 1):
    +# UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname
    +print(user.nickname)  # daisy
    +
    +# get its name again
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +print(name)  # daisy
    +assert name == user.nickname  # they are both 'daisy' after the update
    +
    +
    +

    So update() is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +apply() call makes the update happen in database.

    +
    +

    Note

    +

    GINO explicitly split the in-memory update and SQL update into two methods: +update() and +apply(). update() +will update the in-memory model object and return an +UpdateRequest object which contains all the +modifications. A following apply() on +UpdateRequest object will apply these recorded +modifications to database by executing a compiled SQL.

    +
    +
    +

    Tip

    +

    UpdateRequest object has another method named +update() which works the same as the one +on model object, just that it combines the new modifications together with +the ones already recorded in current UpdateRequest +object, and it returns the same UpdateRequest object. +That means, you can chain the updates and end up with one +apply(), or make use of the +UpdateRequest object to combine several updates in a +batch.

    +
    +

    update() on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the update() on model class level, with a +bit difference:

    +
    await User.update.values(nickname='Founding Member ' + User.nickname).where(
    +    User.id < 10).gino.status()
    +# SQL (parameter: 'Founding Member ', 10):
    +# UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2
    +
    +name = await User.select('nickname').where(
    +    User.id == 1).gino.scalar()
    +print(name)  # Founding Member fantix
    +
    +
    +

    There is no UpdateRequest here, everything is again +SQLAlchemy clause, its +documentation here for +your reference.

    +
    +
    +

    Delete

    +

    At last. Deleting is similar to updating, but way simpler.

    +
    user = await User.create(nickname='fantix')
    +await user.delete()
    +# SQL (parameter: 1):
    +# DELETE FROM users WHERE users.id = $1
    +print(await User.get(user.id))  # None
    +
    +
    +
    +

    Hint

    +

    Remember the model object is in memory? In the last print() +statement, even though the row is already deleted in database, the object +user still exists with its values untouched.

    +
    +

    Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):

    +
    await User.delete.where(User.id > 10).gino.status()
    +# SQL (parameter: 10):
    +# DELETE FROM users WHERE users.id > $1
    +
    +
    +

    With basic CRUD, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking!

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/index.html b/docs/en/index.html new file mode 100644 index 0000000..2f8acf8 --- /dev/null +++ b/docs/en/index.html @@ -0,0 +1,16 @@ + + + + + + + Page Redirection + + + + If you are not redirected automatically, follow this link to GINO docs. + + + diff --git a/docs/en/master/.buildinfo b/docs/en/master/.buildinfo new file mode 100644 index 0000000..db3056b --- /dev/null +++ b/docs/en/master/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: f54bb6ca590d961021f1e65aa9908a09 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/en/master/_images/263px-Minimum-Tonne.svg.png b/docs/en/master/_images/263px-Minimum-Tonne.svg.png new file mode 100644 index 0000000..f431e31 Binary files /dev/null and b/docs/en/master/_images/263px-Minimum-Tonne.svg.png differ diff --git a/docs/en/master/_images/archlinux.webp b/docs/en/master/_images/archlinux.webp new file mode 100644 index 0000000..3e358ef Binary files /dev/null and b/docs/en/master/_images/archlinux.webp differ diff --git a/docs/en/master/_images/community.png b/docs/en/master/_images/community.png new file mode 100644 index 0000000..1aa9693 Binary files /dev/null and b/docs/en/master/_images/community.png differ diff --git a/docs/en/master/_images/community.svg b/docs/en/master/_images/community.svg new file mode 100644 index 0000000..dae6507 --- /dev/null +++ b/docs/en/master/_images/community.svg @@ -0,0 +1,26 @@ + + + + co-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/connection.png b/docs/en/master/_images/connection.png new file mode 100644 index 0000000..e54598d Binary files /dev/null and b/docs/en/master/_images/connection.png differ diff --git a/docs/en/master/_images/docs.webp b/docs/en/master/_images/docs.webp new file mode 100644 index 0000000..20259b1 Binary files /dev/null and b/docs/en/master/_images/docs.webp differ diff --git a/docs/en/master/_images/engine.png b/docs/en/master/_images/engine.png new file mode 100644 index 0000000..6b64561 Binary files /dev/null and b/docs/en/master/_images/engine.png differ diff --git a/docs/en/master/_images/exchangeratesapi.webp b/docs/en/master/_images/exchangeratesapi.webp new file mode 100644 index 0000000..dc2657a Binary files /dev/null and b/docs/en/master/_images/exchangeratesapi.webp differ diff --git a/docs/en/master/_images/explanation.png b/docs/en/master/_images/explanation.png new file mode 100644 index 0000000..1de02e2 Binary files /dev/null and b/docs/en/master/_images/explanation.png differ diff --git a/docs/en/master/_images/explanation.svg b/docs/en/master/_images/explanation.svg new file mode 100644 index 0000000..ef775d1 --- /dev/null +++ b/docs/en/master/_images/explanation.svg @@ -0,0 +1,19 @@ + + + + ex-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-alembic.svg b/docs/en/master/_images/gino-fastapi-alembic.svg new file mode 100644 index 0000000..0cfc4b1 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-alembic.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    alembic.ini
    ale...
    migrations
    migra...
    env.py
    env...
    versions
    versi...
    32c0feba61ea_add_users_table.py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-config.svg b/docs/en/master/_images/gino-fastapi-config.svg new file mode 100644 index 0000000..673d084 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-config.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    config.py
    con...
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-env.svg b/docs/en/master/_images/gino-fastapi-env.svg new file mode 100644 index 0000000..4ec9e4e --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-env.svg @@ -0,0 +1,3 @@ + + +
    .env
    .env
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-layout.svg b/docs/en/master/_images/gino-fastapi-layout.svg new file mode 100644 index 0000000..07f4701 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-layout.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    .env
    .env
    models
    models
    __init__.py
    __i...
    users.py
    use...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    __init__.py
    __i...
    asgi.py
    asg...
    config.py
    con...
    main.py
    mai...
    tests
    tests
    conftest.py
    con...
    test_users.py
    tes...
    migrations
    migra...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    The project root directory.

    Alembic data directory.

    Database migration revisions directory.

    One of the revisions.

    Alembic Python environment.

    Application source code container.

    Project root python package.

    Database models and GINO instance.

    GINO instance (SQLAlchemy Metadata).

    Models for users.

    API implementation.



    User-related APIs.



    ASGI entry point.

    Starlette-style application configuration.

    Application initialization.

    Testing code.

    pytest fixtures.

    User-related tests.

    Local config, not in Git control.

    Alembic entry config.

    Production Docker image.

    Poetry-frozen dependency versions.

    Project and dependency definition.
    The project root directory....
    alembic.ini
    ale...
    Dockerfile
    Doc...
    env.py
    env...
    versions
    versi...
    32c0feba61ea....py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-models-users.svg b/docs/en/master/_images/gino-fastapi-models-users.svg new file mode 100644 index 0000000..886d581 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-models-users.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-models.svg b/docs/en/master/_images/gino-fastapi-models.svg new file mode 100644 index 0000000..4dc983c --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-models.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-poetry.svg b/docs/en/master/_images/gino-fastapi-poetry.svg new file mode 100644 index 0000000..e0da8c1 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-poetry.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-src.svg b/docs/en/master/_images/gino-fastapi-src.svg new file mode 100644 index 0000000..7310163 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-src.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    __init__.py
    __i...
    asgi.py
    asg...
    main.py
    mai...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-tests.svg b/docs/en/master/_images/gino-fastapi-tests.svg new file mode 100644 index 0000000..53673d8 --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-tests.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    tests
    tests
    test_users.py
    tes...
    conftest.py
    con...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi-views.svg b/docs/en/master/_images/gino-fastapi-views.svg new file mode 100644 index 0000000..7321a7a --- /dev/null +++ b/docs/en/master/_images/gino-fastapi-views.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino-fastapi.svg b/docs/en/master/_images/gino-fastapi.svg new file mode 100644 index 0000000..9d365ba --- /dev/null +++ b/docs/en/master/_images/gino-fastapi.svg @@ -0,0 +1,3 @@ + + +
    Gunicorn
    Gunicorn
    Uvicorn
    Uvicorn
    Starlette
    Starlette
    FastAPI
    FastAPI
    API Implementation
    API Implementation
    GINO Models
    GINO Models
    GINO with Starlette
    GINO with Starlette
    SQLAlchemy core
    SQLAlchemy core
    asyncpg
    asyncpg
    Alembic
    Alembic
    psycopg2
    psycopg2
    PostgreSQL
    PostgreSQL
    Application Server
    Application Server
    ASGI Middleware
    ASGI Middleware
    Web Framework
    Web Framework
    Tutorial Code
    Tutorial Code
    GINO
    GINO
    Database Library
    Database Library
    HTTP API
    HTTP API
    DB Migration CLI
    DB Migration CLI
    Legend
    Legend
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/en/master/_images/gino.png b/docs/en/master/_images/gino.png new file mode 100644 index 0000000..9032a58 Binary files /dev/null and b/docs/en/master/_images/gino.png differ diff --git a/docs/en/master/_images/github.png b/docs/en/master/_images/github.png new file mode 100644 index 0000000..7b91181 Binary files /dev/null and b/docs/en/master/_images/github.png differ diff --git a/docs/en/master/_images/github.svg b/docs/en/master/_images/github.svg new file mode 100644 index 0000000..cb2484b --- /dev/null +++ b/docs/en/master/_images/github.svg @@ -0,0 +1,27 @@ + + + + sourcecode + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/happy-hacking.png b/docs/en/master/_images/happy-hacking.png new file mode 100644 index 0000000..701e7bd Binary files /dev/null and b/docs/en/master/_images/happy-hacking.png differ diff --git a/docs/en/master/_images/how-to.png b/docs/en/master/_images/how-to.png new file mode 100644 index 0000000..b643424 Binary files /dev/null and b/docs/en/master/_images/how-to.png differ diff --git a/docs/en/master/_images/how-to.svg b/docs/en/master/_images/how-to.svg new file mode 100644 index 0000000..7461024 --- /dev/null +++ b/docs/en/master/_images/how-to.svg @@ -0,0 +1,22 @@ + + + + guide + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/open-source.png b/docs/en/master/_images/open-source.png new file mode 100644 index 0000000..f2327c0 Binary files /dev/null and b/docs/en/master/_images/open-source.png differ diff --git a/docs/en/master/_images/open-source.svg b/docs/en/master/_images/open-source.svg new file mode 100644 index 0000000..0946f0a --- /dev/null +++ b/docs/en/master/_images/open-source.svg @@ -0,0 +1,18 @@ + + + + bsd + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/python-gino.webp b/docs/en/master/_images/python-gino.webp new file mode 100644 index 0000000..f6c11ac Binary files /dev/null and b/docs/en/master/_images/python-gino.webp differ diff --git a/docs/en/master/_images/python.png b/docs/en/master/_images/python.png new file mode 100644 index 0000000..bb01dbc Binary files /dev/null and b/docs/en/master/_images/python.png differ diff --git a/docs/en/master/_images/python.svg b/docs/en/master/_images/python.svg new file mode 100644 index 0000000..f62b29b --- /dev/null +++ b/docs/en/master/_images/python.svg @@ -0,0 +1,22 @@ + + + + download + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/reference.png b/docs/en/master/_images/reference.png new file mode 100644 index 0000000..fdc4dae Binary files /dev/null and b/docs/en/master/_images/reference.png differ diff --git a/docs/en/master/_images/reference.svg b/docs/en/master/_images/reference.svg new file mode 100644 index 0000000..0c5f656 --- /dev/null +++ b/docs/en/master/_images/reference.svg @@ -0,0 +1,19 @@ + + + + re-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/tutorials.png b/docs/en/master/_images/tutorials.png new file mode 100644 index 0000000..b87dbfd Binary files /dev/null and b/docs/en/master/_images/tutorials.png differ diff --git a/docs/en/master/_images/tutorials.svg b/docs/en/master/_images/tutorials.svg new file mode 100644 index 0000000..2eb2111 --- /dev/null +++ b/docs/en/master/_images/tutorials.svg @@ -0,0 +1,26 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_images/why_coroutine.png b/docs/en/master/_images/why_coroutine.png new file mode 100644 index 0000000..99c743a Binary files /dev/null and b/docs/en/master/_images/why_coroutine.png differ diff --git a/docs/en/master/_images/why_multicore.png b/docs/en/master/_images/why_multicore.png new file mode 100644 index 0000000..c209a9f Binary files /dev/null and b/docs/en/master/_images/why_multicore.png differ diff --git a/docs/en/master/_images/why_multithreading.png b/docs/en/master/_images/why_multithreading.png new file mode 100644 index 0000000..e0e4321 Binary files /dev/null and b/docs/en/master/_images/why_multithreading.png differ diff --git a/docs/en/master/_images/why_single_task.png b/docs/en/master/_images/why_single_task.png new file mode 100644 index 0000000..54fab08 Binary files /dev/null and b/docs/en/master/_images/why_single_task.png differ diff --git a/docs/en/master/_images/why_throughput.png b/docs/en/master/_images/why_throughput.png new file mode 100644 index 0000000..388f533 Binary files /dev/null and b/docs/en/master/_images/why_throughput.png differ diff --git a/docs/en/master/_sources/explanation.rst.txt b/docs/en/master/_sources/explanation.rst.txt new file mode 100644 index 0000000..6d91357 --- /dev/null +++ b/docs/en/master/_sources/explanation.rst.txt @@ -0,0 +1,7 @@ +Explanation +=========== + +.. toctree:: + :glob: + + explanation/* diff --git a/docs/en/master/_sources/explanation/async.rst.txt b/docs/en/master/_sources/explanation/async.rst.txt new file mode 100644 index 0000000..a857170 --- /dev/null +++ b/docs/en/master/_sources/explanation/async.rst.txt @@ -0,0 +1,214 @@ +Asynchronous Programming 101 +============================ + + +The Story +--------- + +Let's say we want to build a search engine. We'll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this: + +.. image:: ../images/why_single_task.png + :align: center + +We have lots of web pages to index, so we simply handle them one by one: + +.. image:: ../images/why_throughput.png + :align: center + +Let's assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores: + +.. image:: ../images/why_multicore.png + :align: center + +This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading: + +.. image:: ../images/why_multithreading.png + :align: center + +Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What's wrong with multi-threading? From the diagram we can see: + +* There are yellow bars taking up extra time. +* The green bars can still overlap with any bar in the other thread, but +* non-green bars cannot overlap with non-green bars in the other thread. + +The yellow bars are time taken by `context switches +`_, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let's assume a world without +`Hyper-threading `_ or similar), +so in order to run several threads concurrently the CPU must `split its +time `_ into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point. + +Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it's waiting for the HTTP response (I/O). That's how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won't be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That's also why this is called concurrency instead of parallelism. + +As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous `C10k +problem `_, usually solved by +asynchronous I/O: + +.. image:: ../images/why_coroutine.png + :align: center + +.. note:: + + Asynchronous I/O and coroutines are two different things, but they usually + go together. Here we will stick with coroutines for simplicity. + +Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem. + + +Cooperative multitasking +------------------------ + +So what is a coroutine? + +In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread. + +Threads are scheduled by the operating system using an approach called `preemptive +multitasking `_. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn't +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this: + +.. code-block:: none + + Thread 1: I wanna run! + OS: Okay, here you go... + Thread 2: I wanna run! + OS: Urh, alright one sec ... Thread 1, hold on for a while! + Thread 1: Well I'm not done yet, but you are the boss. + OS: It won't be long. Thread 2 it's your turn now. + Thread 2: Yay! (&%#$@..+*&#) + Thread 1: Can I run now? + OS: Just a moment please ... Thread 2, give it a break! + Thread 2: Alright ... but I really need the CPU. + OS: You'll have it later. Thread 1, hurry up! + +In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don't - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +`cooperative multitasking +`_. It's like this: + +.. code-block:: none + + Coroutine 1: Let me know when event A arrives. I'm done here before that. + Event manager: Okay. What about you, coroutine 2? + Coroutine 2: Um I've got nothing to do here before event B. + Event manager: Cool, I'll be watching. + Event manager: (after a while) Hey coroutine 1, event A is here! + Coroutine 1: Awesome! Let me see ... looks good, but I need event C now. + Event manager: Very well. Seems event B arrived just now, coroutine 2? + Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done. + Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while. + (silence) + Event manager: Damn, event C timed out! + Coroutine 1: Arrrrh gotta kill myself with an exception :S + Event manager: Up to you :/ + +For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That's why in the last diagram, +the red bars are not interleaved like threads. + +.. tip:: + + In Python and asyncio, ``async def`` declares coroutines, ``await`` yields + control to event loop (event manager). + + +Pros and cons +------------- + +Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput. + +With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code. + +However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple ``recv()`` operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to ``recv()``, repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop_ this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O. + +Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, ``sleep(1)`` +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between ``await`` finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind. + +Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It's not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure. + + +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/en/master/_sources/explanation/engine.rst.txt b/docs/en/master/_sources/explanation/engine.rst.txt new file mode 100644 index 0000000..ebd59ed --- /dev/null +++ b/docs/en/master/_sources/explanation/engine.rst.txt @@ -0,0 +1,521 @@ +===================== +Engine and Connection +===================== + +:class:`~gino.engine.GinoEngine` is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together: + +.. image:: ../images/engine.png + :align: center + +Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users. + +During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use. + +.. note:: + + In SQLAlchemy, database drivers are supposed to follow the DB-API standard, + which does not usually provide a pool implementation. Therefore, SQLAlchemy + has its own pool implementation, created directly in engine. This is where + this diagram doesn't fit SQLAlchemy. + +The pool creates raw connections, not the :class:`~gino.engine.GinoConnection` +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries. + +On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use. + +.. note:: + + Another difference to SQLAlchemy here: GINO execution methods always return + final results, while in SQLAlchemy accessing the result may cause further + implicit database accesses. Therefore GINO engine immediately releases the + connection when the execution method on the engine returns, but SQLAlchemy + can only release the connection implicitly when the result data is found + exhausted. + + By immediately releasing a connection, GINO may not release the related raw + connection when the raw connection was reused from another parent + connection. We'll get to this later. + +GINO also supports `implicit execution +`_ +without having to specify an engine or connection explicitly. This is done by +binding the engine to the ``db`` instance, also known as the +:class:`~sqlalchemy.schema.MetaData` or the :class:`~gino.api.Gino` instance. +You may possibly bind a :class:`~gino.engine.GinoConnection` instance, but that +is greatly not recommended because it is very much untested. + +At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +``db`` instance on creation, therefore the models can do implicit execution too +if their ``db`` has a bind. + +Then let's get to some details. + + +Creating Engines +---------------- + +GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous :class:`~gino.engine.GinoEngine` is +just ``gino``, but only available after ``gino`` is imported:: + + import gino, sqlalchemy + + async def main(): + e = await sqlalchemy.create_engine('postgresql://...', strategy='gino') + # e is a GinoEngine + +.. tip:: + + Please read `this SQLAlchemy document + `_ + to learn about writing database URLs. + +Also the GINO strategy replaces the default driver of dialect ``postgresql://`` +from ``psycopg2`` to ``asyncpg``, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +``postgresql+asyncpg://...`` or just ``asyncpg://...``. + +GINO also offers a shortcut as :func:`gino.create_engine`, which only sets the +default strategy to ``gino`` and does nothing more. So here is an identical +example:: + + import gino + + async def main(): + e = await gino.create_engine('postgresql://...') + # e is also a GinoEngine + +As you may have noticed, when using the GINO strategy, +:func:`~sqlalchemy.create_engine` returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default. + +For it is just SQLAlchemy :func:`~sqlalchemy.create_engine`, the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!): + +For Dialect: + +* `isolation_level `_ +* `paramstyle `_ + +For Engine: + +* `echo `_ +* `execution_options `_ +* `logging_name `_ + +While these parameters are discarded by GINO: + +* `module `_ + +In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from :func:`~asyncpg.pool.create_pool`. +For example, we can create an engine without initial connections:: + + e = await gino.create_engine('postgresql://...', min_size=0) + +Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:: + + import sqlalchemy + + metadata = sqlalchemy.MetaData() + metadata.bind = 'postgresql://...' + + # or in short + + metadata = sqlalchemy.MetaData('postgresql://...') + +This implicitly calls :func:`~sqlalchemy.create_engine` under the hood. However +in GINO, creating an engine requires ``await``, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass :class:`~gino.api.Gino`, reverted it to simple assignment:: + + import gino + + db = gino.Gino() + + async def main(): + # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind + engine = await gino.create_engine('postgresql://...') + db.bind = engine + +And provided a shortcut to do so:: + + engine = await db.set_bind('postgresql://...') + +And another simpler shortcut for one-time usage:: + + db = await gino.Gino('postgresql://...') + +To unset a bind and close the engine:: + + engine, db.bind = db.bind, None + await engine.close() + +Or with a shortcut correspondingly:: + + await engine.pop_bind().close() + +Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +Managing Connections +-------------------- + +With a :class:`~gino.engine.GinoEngine` at hand, you can acquire connections +from the pool now:: + + conn = await engine.acquire() + +Don't forget to release it after use:: + + await conn.release() + +Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:: + + async with engine.acquire() as conn: + # play with the connection + +Here ``conn`` is a :class:`~gino.engine.GinoConnection` instance. As mentioned +previously, :class:`~gino.engine.GinoConnection` is mapped to an underlying raw +connection, as shown in following diagram: + +.. image:: ../images/connection.png + :align: center + +Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: ``reuse`` and +``lazy``. They are keyword arguments on :meth:`~gino.engine.GinoEngine.acquire` +and by default switched off. + +reuse +""""" + +When acquiring a :class:`~gino.engine.GinoConnection` (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +:class:`~gino.engine.GinoConnection` (2). This is the default behavior of +:meth:`~gino.engine.GinoConnection.acquire` with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:: + + async with engine.acquire() as conn1: + async with engine.acquire() as conn2: + # conn2 is a completely different connection than conn1 + +But sometimes ``conn2`` may exist in a different method:: + + async def outer(): + async with engine.acquire() as conn1: + await inner() + + async def inner(): + async with engine.acquire() as conn2: + # ... + +And we probably wish ``inner`` could reuse the same raw connection in +``outer`` to save some resource, or borrow a new one if ``inner`` is +individually called without ``outer``:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(conn2=None): + if conn2 is None: + async with engine.acquire() as conn2: + # ... + else: + # the same ... again + +This is exactly the scenario ``reuse`` could be useful. We can simply tell the +:meth:`~gino.engine.GinoConnection.acquire` to reuse the most recent reusable +connection in current context by setting ``reuse=True``, as presented in this +identical example:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(): + async with engine.acquire(reuse=True) as conn2: + # ... + +Back to previous diagram, the blue :class:`~gino.engine.GinoConnection` +instances (3, 4, 6) are "reusing connections" acquired with ``reuse=True``, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that ``acquire(reuse=True)`` always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack. + +.. tip:: + + By context, we are actually referring to the context concept in + `contextvars `_ the + new module in Python 3.7, and its partial backport `aiocontextvars + `_. Simply speaking, you may + treat a series of function calls in a chain as in the same context, even if + there is an ``await``. It's something like a thread local in asyncio. + +:class:`~gino.engine.GinoConnection` (2) may be created through +``acquire(reuse=True)`` too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection. + +Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +:class:`~gino.engine.GinoConnection` can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more. + +lazy +"""" + +As you may have found, :class:`~gino.engine.GinoConnection` (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set ``lazy=True`` on acquire. + +A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, :class:`~gino.engine.GinoConnection` (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + +On implementation level, ``lazy`` is extremely easy in +:meth:`~gino.engine.GinoEngine.acquire`: if ``lazy=False`` then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, :class:`~gino.egnine.GinoConnection` will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all :class:`~gino.engine.GinoConnection` +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + await conn.release(permanent=False) # release (8) + await asyncio.sleep(10) # simulate long I/O work + await conn.scalar('select now()') # re-acquire a new raw connection, + # not necessarily the same (8) + +When used together with ``reuse``, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set ``lazy=False`` on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again: + +.. image:: ../images/connection.png + :align: center + + +reusable +"""""""" + +Usually, you don't have to worry about the two options ``reuse`` and ``lazy``, +using the default :meth:`~gino.engine.GinoEngine.acquire` will always create +a concrete :class:`~gino.engine.GinoConnection` with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use ``reusable=False`` on acquire. As shown in the diagram, the unreusable +:class:`~gino.engine.GinoConnection` is an orphan away from any stack:: + + async with engine.acquire(): # (2) + async with engine.acquire(reusable=False): # the unreusable connection + async with engine.acquire(reuse=True): # (3) + +Unreusable connections can be lazy. But it is usually meaningless to specify +both ``reuse=True`` and ``reusable=False`` at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +``acquire(reuse=True, reusable=False)`` unless you know what it does. + + +current_connection +"""""""""""""""""" + +Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +:attr:`~gino.engine.GinoEngine.current_connection` which is always the reusable +:class:`~gino.engine.GinoConnection` at the top of current stack, or ``None`` +if current stack is empty. + +.. tip:: + + The different between :attr:`~gino.engine.GinoEngine.current_connection` + and :meth:`acquire(reuse=True) ` is, the + latter always produces a :class:`~gino.engine.GinoConnection`, while the + former may not. + + +Executing Queries +----------------- + +Once you have a :class:`~gino.engine.GinoConnection` instance, you can start +executing queries with it. There are 6 variants of the execute method: +:meth:`~gino.engine.GinoConnection.all`, +:meth:`~gino.engine.GinoConnection.first`, +:meth:`~gino.engine.GinoConnection.one`, +:meth:`~gino.engine.GinoConnection.one_or_none`, +:meth:`~gino.engine.GinoConnection.scalar` and +:meth:`~gino.engine.GinoConnection.status`. They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results: + +* :meth:`~gino.engine.GinoConnection.all` returns all results in a + :class:`list`, which may be empty when the query has no result, empty but + still a :class:`list`. +* :meth:`~gino.engine.GinoConnection.first` returns the first result directly, + or ``None`` if there is no result at all. There is usually some optimization + behind the scene to efficiently get only the first result, instead of loading + the full result set into memory. +* :meth:`~gino.engine.GinoConnection.one` returns exactly one result. If there + is no result at all or if there are multiple results, an exception is raised. +* :meth:`~gino.engine.GinoConnection.one_or_none` is similar to + :meth:`~gino.engine.GinoConnection.one`, but it returns ``None`` if there is + no result instead or raising an exception. +* :meth:`~gino.engine.GinoConnection.scalar` is similar to + :meth:`~gino.engine.GinoConnection.first`, it returns the first value of the + first result. Quite convenient to just retrieve a scalar value from database, + like ``NOW()``, ``MAX()``, ``COUNT()`` or whatever generates a single value. + ``None`` is also returned when there is no result, it is up to you how to + distinguish no result and the first value is ``NULL``. +* :meth:`~gino.engine.GinoConnection.status` executes the query and discard all + the query results at all. Instead it returns the execution status line as it + is, usually a textual string. Note, there may be no optimization to only + return the status without loading the results, so make your query generate + nothing if you don't want any result. + +By "result", I meant :class:`~sqlalchemy.engine.RowProxy` of SQLAlchemy - an +immutable row instance with both :class:`tuple` and :class:`dict` interfaces. +Database values are translated twice before they are eventually stored in a +:class:`~sqlalchemy.engine.RowProxy`: first by the database driver (dialect) +from network payload to Python objects (see `Type Conversion +`_ of +how asyncpg does this), second by SQLAlchemy +:meth:`~sqlalchemy.types.TypeEngine.result_processor` depending on the actual +type and dialect. + +The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy :meth:`~sqlalchemy.engine.Connection.execute` (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +``multiparams``, all 4 methods will always return ``None`` discarding all +results. Likewise, the parameter values are processed twice too: first by +:meth:`~sqlalchemy.types.TypeEngine.bind_processor` then the database driver. + +GINO also supports SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execution_options` provided either on +:meth:`engine level `, +:meth:`connection level ` or on +:meth:`queries `. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling ``return_model`` and providing a ``model`` will make +:meth:`~gino.engine.GinoConnection.all` and +:meth:`~gino.engine.GinoConnection.first` return ORM model instance(s) instead +of :class:`~sqlalchemy.engine.RowProxy` instance(s). See also +:meth:`~sqlalchemy.engine.Connection.execution_options` for more information. + +In addition, GINO has an :meth:`~gino.engine.GinoConnection.iterate` method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a `server-side cursor +`_. + + +Implicit Execution +------------------ + +Acquire a :class:`~gino.engine.GinoConnection` and execute queries on it, that +is the most explicit way. You can also execute queries on a +:class:`~gino.engine.GinoEngine` instance. In this case, a connection will be +acquired with ``reuse=True`` for you implicitly, and released after returning:: + + await engine.scalar('select now()') + +Equals to:: + + async with engine.acquire(reuse=True) as conn: + await conn.scalar('select now()') + +This allows you to easily write connectionless code. For example:: + + async def get_now(): + return await engine.scalar('select now()') + + async def main(): + async with engine.acquire(): + now = await get_now() + await engine.status('UPDATE ...') + +In this example, ``main()`` will take only one raw connection. ``get_now()`` +can also work alone out of any ``acquire()`` context, thanks to ``reuse``. + +Furthermore, GINO provides the same query APIs on :class:`~gino.api.Gino` +directly. They are simply delegates to corresponding API methods on the +``bind``. This allows even engine-less programming:: + + db = gino.Gino() + + async def get_now(): + return await db.scalar('select now()') + + async def main(): + async with db.with_bind('postgresql://...'): + now = await get_now() + await db.status('UPDATE ...') + +.. note:: + + In this example we didn't put the two queries in an ``acquire()`` block, so + they might be executed in two different connections. + +At last, the SQLAlchemy `implicit execution +`_ +on queries also work in GINO, under an extension named ``gino``:: + + await users_table.select().gino.all() + +By default, the extension :class:`~gino.api.GinoExecutor` is injected on +:class:`~sqlalchemy.sql.expression.Executable` as a property of name ``gino`` +at the creation of :class:`~gino.api.Gino` instance. Therefore, any +:class:`~sqlalchemy.sql.expression.Executable` object has the ``gino`` +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the ``bind`` of the ``db`` instance. diff --git a/docs/en/master/_sources/explanation/sa20.rst.txt b/docs/en/master/_sources/explanation/sa20.rst.txt new file mode 100644 index 0000000..0c18c1f --- /dev/null +++ b/docs/en/master/_sources/explanation/sa20.rst.txt @@ -0,0 +1,691 @@ +SQLAlchemy 2.0 +============== + +This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes. + +`SQLAlchemy 2.0 `__ will +deliver many breaking API changes, and `SQLAlchemy 1.4 +`__ will be the "interim" +version for people to eventually upgrade their software to use SQLAlchemy 2.0. + ++-------+------------+----------+----------------------------+ +| GINO | SQLAlchemy | Dialect | Comments | ++=======+============+==========+============================+ +| 1.0.x | 1.3.x | Custom | Current (old-)stable. | ++-------+------------+----------+----------------------------+ +| 1.1.x | 1.3.x | Custom | Next old-stable. | ++-------+------------+----------+----------------------------+ +| 1.2.x | 1.3.x | Custom | Future old-stable (maybe). | ++-------+------------+----------+----------------------------+ +| 1.4.x | 1.4.x | Upstream | 2.0 Interim. | ++-------+------------+----------+----------------------------+ +| 2.0.x | 2.0.x | Upstream | Future stable. | ++-------+------------+----------+----------------------------+ +| 2.1.x | 2.0.x | Upstream | Future stable iterations. | ++-------+------------+----------+----------------------------+ + +To make things easier, GINO will (luckily) also `follow the same versions +`__ for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only. + +At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won't match SQLAlchemy versions. + + +The Async Solution +------------------ + +Among all the exciting updates in SQLAlchemy 1.4 / 2.0, `native async support +`__ is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet_ to mix asynchronous stuff into current code base, avoiding making everything +async. + +Let's say we have an asynchronous method to create an asyncpg connection:: + + import asyncpg + + async def connect(): + return await asyncpg.connect("postgresql:///") + +And an end-user method to use it:: + + async def main(): + conn = await connect() + now = await conn.fetchval("SELECT now()") + +Now instead of directly calling ``connect()`` from ``main()``, I would like to add some +additional logic - let's say, a sanity check:: + + async def safe_connect(): + conn = await connect() + try: + await conn.execute("SELECT 1") + except Exception: + return None + else: + return conn + +Then the end-user should modify ``main()`` to: + +.. code-block:: python + :emphasize-lines: 2,3 + + async def main(): + conn = await safe_connect() + if conn: + now = await conn.fetchval("SELECT now()") + +OK, everything works so far, as they are all regular async code. Here's the interesting +part: ``safe_connect()`` must not be an ``async def`` method. With SQLAlchemy 1.4+, we +could: + +.. code-block:: python + :emphasize-lines: 1,3,4,6,13 + + from sqlalchemy.util import await_only, greenlet_spawn + + def sync_safe_connect(): + conn = await_only(connect()) + try: + await_only(conn.execute("SELECT 1")) + except Exception: + return None + else: + return conn + + async def safe_connect(): + return await greenlet_spawn(sync_safe_connect) + +Behind the scene, ``greenlet_spawn()`` runs the given "sync" method in a greenlet, which +uses ``await_only()`` to switch to the event loop and bridge the underlying async +methods. As ``sync_safe_connect()`` is just a normal Python method, you can imagine how +it works together with lots of other "sync" code asynchronously. + +We're not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its "sync" code base, and still being able to provide async +APIs on top of them. + + +Async SQLAlchemy +---------------- + +Although greenlet_ might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move. + +The sync library existed for years, with many assumptions like using ``threading.Lock`` +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. ``asyncio.Lock``. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues. + +As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, ``threading.Lock.acquire()`` actually works fine in a single coroutine, but `2 +concurrent coroutines `__ +acquiring the same ``threading.Lock`` may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread. + +Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base. + +However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that's GINO's part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won't work +because there is no place for ``await`` in accessing attributes (GINO doesn't like such +implicitness anyways, so yeah). + +To quickly get a picture of async SQLAlchemy (Core), here's a sample from SQLAlchemy:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + + async with engine.begin() as conn: + await conn.run_sync(meta.drop_all) + await conn.run_sync(meta.create_all) + + await conn.execute( + t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}] + ) + + async with engine.connect() as conn: + + # select a Result, which will be delivered with buffered + # results + result = await conn.execute(select(t1).where(t1.c.name == "some name 1")) + + print(result.fetchall()) + + + asyncio.run(async_main()) + + +Auto-Commit Complication +------------------------ + +After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that ``BEGIN`` starts a transaction and +``COMMIT`` / ``ROLLBACK`` ends it. But what is happening to SQL statements that is not +wrapped in ``BEGIN ... COMMIT`` blocks? + + If you do not issue a ``BEGIN`` command, then each individual statement has an + implicit ``BEGIN`` and (if successful) ``COMMIT`` wrapped around it. + + -- PostgreSQL Documentation, `3.4. Transactions + `__ + +And yes, implicit ``ROLLBACK`` if not successful. This is not directly named as an +"auto-commit" feature, but PostgreSQL does enforce it. Now imagine a database whose +"auto-commit" feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed. + +`PEP 249 `__ (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only ``commit()`` and ``rollback()`` on a +connection, but no ``begin()``. So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call ``commit()`` to persist your changes. Closing a connection will +cause pending transactions rolled back automatically. + + Note that if the database supports an auto-commit feature, this (*the auto-commit + feature -- GINO comments*) must be initially off. + + -- PEP 249 + +As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +"auto-commit" has to mimic such behavior. For example, psycopg2_ will automatically emit +a ``BEGIN`` to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen ``IDLE IN TRANSACTION``?), sometimes even +holding database locks and eventually causing a deadlock storm. + +To work around this workaround, PEP 249 does say: + + An interface method may be provided to turn it (the auto-commit feature) back on. + +So for psycopg2_, one could do this:: + + import psycopg2 + + conn = psycopg2.connect("postgresql:///") + conn.autocommit = True + conn.cursor().execute("SELECT now()") + +Now the database correctly receives this ``SELECT`` statement only, without any implicit +``BEGIN`` surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:: + + conn.cursor().execute("BEGIN") + conn.cursor().execute("UPDATE ...") + conn.cursor().execute("COMMIT") + +Or 2) turn auto-commit off again:: + + conn.autocommit = False + conn.cursor().execute("UPDATE ...") + conn.commit() + +I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg_ does provide a cleaner API, by not complying to PEP 249:: + + import asyncpg + + async def main(): + conn = await asyncpg.connect("postgresql://") + + print(await conn.fetchval("SELECT now()")) # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.execute("UPDATE ...") # UPDATE ...; + # COMMIT; + +It's much cleaner to see what's actually happening on the wire to the database. This is +also how GINO works. + + +SQLAlchemy for DB-API +--------------------- + +Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:: + + import sqlalchemy as sa + + e = sa.create_engine("postgresql:///", future=True) + with e.connect() as conn: + conn.scalar(sa.text("SELECT now()")) + +Only ``SELECT now()``? No. Here's the answer:: + + with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + conn.scalar(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +.. note:: + + We are using SQLAlchemy 2.0 API for simplification, by setting ``future=True`` using + SQLAlchemy 1.4. It's way more complicated in SQLAlchemy 1.3 and I don't want to get + into that. + +The reason behind this is, connections have "auto-commit" turned off by default. If +there is no transaction, an implicit ``BEGIN`` will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg_ +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg_ and +simulated a compatible DB-API. Like this:: + + import sqlalchemy as sa + from sqlalchemy.ext.asyncio import create_async_engine + + async def main(): + e = create_async_engine("postgresql+asyncpg:///") + async with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +If you want to modify the database permanently, you have to ``commit()`` the implicit +transaction explicitly: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("UPDATE ...")) # BEGIN; UPDATE ...; + await conn.commit() # COMMIT; + +Or use the explicit transaction API: + +.. code-block:: python + :emphasize-lines: 4,6 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Please note that, SQLAlchemy 2.0 doesn't allow soft-nested transactions. In other words, +you cannot nest 2 ``async with conn.begin():`` blocks like this: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + async with conn.begin(): # Error: a transaction is already begun + ... + +This limitation applies to implicit transactions too, even though it's weird: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + await conn.rollback() # ROLLBACK; + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Similar to Core, SQLAlchemy ORM `follows the same principal +`__. Grab a session, use +it without ``begin()``, and when you want to commit, ``commit()``. Or, use an explicit +transaction in a ``with session.begin():`` block. Personally I don't like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more. + + +SQLAlchemy AUTOCOMMIT +--------------------- + +I know you already miss the WYSIWYG asyncpg_ and GINO API. Hang in there, let's build +GINO 1.4 together with the `SQLAlchemy AUTOCOMMIT feature +`__. + +To turn AUTOCOMMIT back on, we need to set the ``isolation_level`` to ``AUTOCOMMIT`` in +``execution_options``: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Hooray! No more implicit ``BEGIN`` magic. We're one step closer. + +.. note:: + + There is also a keyword argument: + + .. code-block:: python + :emphasize-lines: 3 + + e = create_async_engine( + "postgresql+asyncpg:///", + isolation_level="AUTOCOMMIT", + ) + + But this is implemented very differently than ``execution_options``, and I don't + think it's working for GINO's use case. + +The next question is, how do we explicitly start a transaction? Let's try ``begin()``: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +Wait a second ... we've seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we're in AUTOCOMMIT mode? Even +though the ``isolation_level`` tell the driver not to send ``BEGIN`` to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction: + +.. code-block:: python + :emphasize-lines: 5,6 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.begin(): # no-op + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + +Well, not quite what we expected. With AUTOCOMMIT set, all of ``begin()``, ``commit()`` +and ``rollback()`` become no-ops. + +Similar to the answers in psycopg2_, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let's try 2): + +.. code-block:: python + :emphasize-lines: 6-8 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +It's working! According to SQLAlchemy docs, ``execution_options()`` creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well... + +.. code-block:: python + :emphasize-lines: 11,12 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same "DB-API" connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting ``isolation_level`` +modifies the value on "DB-API" connection. + +Returning a SQLAlchemy connection back to the pool resets the ``isolation_level`` to its +default value, and acquiring the same connection again will initialize the +``isolation_level`` with values from ``execution_options`` of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +``isolation_level`` again: + +.. code-block:: python + :emphasize-lines: 11 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + conn.execution_options(isolation_level="AUTOCOMMIT") + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Eventually we made it! 🎉 + +Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:: + + import gino + + async def main(): + engine = await gino.create_engine("postgresql:///") + async with engine.acquire() as conn: + await conn.scalar("SELECT now()") # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.status("UPDATE ...") # UPDATE ...; + # COMMIT; + +.. hint:: + + Now I feel that "implementing" auto-commit feature is more like restoring to the + original database behavior, and having auto-commit turned off by default should be + considered as a new feature called "auto-begin" or "implicit transaction". And it's + a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem. + + +Isolation Levels +---------------- + +By far, we only used 2 ``isolation_level`` values: + +* ``AUTOCOMMIT`` +* ``READ COMMITTED`` + +``AUTOCOMMIT`` is not a valid PostgreSQL isolation level. It's only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the "auto-begin" simulation. + +``READ COMMITTED`` is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction: + +.. code-block:: plpgsql + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +To start a transaction in a different isolation level, you may: + +.. code-block:: plpgsql + :emphasize-lines: 1,7 + + # BEGIN ISOLATION LEVEL SERIALIZABLE; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + +As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit ``BEGIN`` in place, an implicit transaction is used. So this SQL also works +individually: + +.. code-block:: plpgsql + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + +But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels: + +.. code-block:: plpgsql + :emphasize-lines: 1,7,10,16,22,28 + + # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE; + SET + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + + # BEGIN ISOLATION LEVEL READ COMMITTED; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +Then let's see how SQLAlchemy with asyncpg solves this problem: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "SERIALIZABLE"}, + ) + async with e.connect() as conn: + async with conn.begin(): # BEGIN ISOLATION LEVEL SERIALIZABLE; + await conn.execute(sa.text("UPDATE ...")) # UPDATE ...; + # COMMIT; + +Under the neath, SQLAlchemy is leveraging asyncpg's +``Connection.transaction(isolation="...")`` to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions. + +But there are 2 issues: + +* User-defined isolation level is not applied in PostgreSQL implicit transactions + (a.k.a. auto-commit statements), because no one ``SET SESSION``. +* asyncpg has a bug that ``Connection.transaction(isolation="read_committed")`` always + emit ``BEGIN`` without explicit isolation level, regardless of the actual default + isolation level. + +The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL: + +.. code-block:: python + :emphasize-lines: 13-20,23,24 + + import sqlalchemy as sa + from sqlalchemy import event + from sqlalchemy.dialects.postgresql.base import PGDialect + from sqlalchemy.ext.asyncio import create_async_engine + + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + + def set_isolation_level(dbapi_conn, record): + PGDialect.set_isolation_level( + e.sync_engine.dialect, + dbapi_conn, + "SERIALIZABLE", + ) + + event.listen(e.sync_engine, "connect", set_isolation_level) + + async with e.connect() as conn: + print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL"))) + # Outputs: serializable + + +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ +.. _psycopg2: https://www.psycopg.org/docs/ +.. _asyncpg: https://github.com/MagicStack/asyncpg diff --git a/docs/en/master/_sources/explanation/why.rst.txt b/docs/en/master/_sources/explanation/why.rst.txt new file mode 100644 index 0000000..77455b5 --- /dev/null +++ b/docs/en/master/_sources/explanation/why.rst.txt @@ -0,0 +1,244 @@ +Why Asynchronous ORM? +===================== + +Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly. + + +When asyncio Helps +------------------ + +As Mike Bayer - the author of SQLAlchemy - `pointed out +`__, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM". + +The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +:doc:`async`. So the ultimate reason to use asyncio should be the application itself, +not the database. + +For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead. + +.. image:: ../images/263px-Minimum-Tonne.svg.png + :align: right + +Another example is authentication using `OpenID Connect Authorization Code Flow +`__ as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +`Liebig's law `__, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave. + +Arbitrary delays may happen in the database too. In PostgreSQL, you can |LISTEN|_ to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case. + +In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is: + +.. |LISTEN| replace:: ``LISTEN`` +.. _LISTEN: https://www.postgresql.org/docs/current/sql-listen.html + + +How to Access Database +---------------------- + +The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here. + +Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:: + + import asyncio + from fastapi import FastAPI + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + app = FastAPI() + e = create_engine("postgresql://localhost") + Session = sessionmaker(bind=e) + + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +This follows advices found in :ref:`session_faq_whentocreate` from SQLAlchemy - linking +the **session scope** to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is ``kill -9``. + +What just happened is called a `resource starvation +`__. While the first 10 +requests were waiting for the async work (``sleep(0.1)``), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default ``max_overflow=10``) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition. + +The root cause of such starvation is making asynchronous calls in a **transaction +scope** created implicitly by the default ``autocommit=False``. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in **transaction scopes** by explicitly ending them:: + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + session.rollback() # return the connection to avoid starvation + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +Because of the implicit nature of typical ORMs, it's very hard to identify all such +**transaction scopes** and make sure there's no ``await`` in them - you may have started +an implicit transaction by simply accessing a property like ``current_user.name``. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this. + +What about thread pool? + +The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool. + +This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery_. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design. + +In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs. + +.. _Celery: http://www.celeryproject.org/ + + +Asynchronous ORM Done Right +--------------------------- + +I would describe a proper asynchronous ORM as follows: + + +Don't Starve. +^^^^^^^^^^^^^ + +The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything ``async``. + +In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:: + + @app.get("/") + async def read_root(): + async with db.acquire() as conn: # } + async with conn.begin(): # } These two won't block the main thread + now = await db.scalar("SELECT now()") + await asyncio.sleep(0.1) # do some async work + return str(now) + +Even though this code won't cause any resource starvation, using ``await`` within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages: + +1. As the database connection pool is usually much smaller than the asynchronous server + concurrency, this kind of code caps the concurrency down to the DB pool level. +2. Taking a database connection from the pool for nothing is a waste of resource, + especially when the database pool is the shortest stave. +3. Database transactions should be kept short as much as possible. Because long-hanging + transactions may keep certain database locks, leading to performance issues or even + triggering a chain reaction of deadlocks. + + +Be explicit. +^^^^^^^^^^^^ + +With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an ``await`` or ``async with``, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following `the pattern of Twisted +`__, asyncio is already forcing +explicit ``await`` for any asynchronous operations. + +Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example: + +* We could design the ORM model instances to be stateless, so that the users don't have + to learn and worry about maintaining the state of the instances. +* There shouldn't be any "buffered operations" which users could "flush" with a single + statement once for all. +* The user should just give direct one-off commands and the ORM executes them right away. +* Also I think the convenience tooling should be well-balanced, the user doesn't have to + guess or remember what an API means - for example, there're more than one ways to load + a many-to-one relationship, I'd prefer to write the query by myself rather than trying + to remember what "join_without_n_plus_1()" means. + + +Be productive. +^^^^^^^^^^^^^^ + +Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two? + +I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design. + +Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again. diff --git a/docs/en/master/_sources/how-to.rst.txt b/docs/en/master/_sources/how-to.rst.txt new file mode 100644 index 0000000..00a13df --- /dev/null +++ b/docs/en/master/_sources/how-to.rst.txt @@ -0,0 +1,7 @@ +How-to Guides +============= + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/en/master/_sources/how-to/alembic.rst.txt b/docs/en/master/_sources/how-to/alembic.rst.txt new file mode 100644 index 0000000..aa60714 --- /dev/null +++ b/docs/en/master/_sources/how-to/alembic.rst.txt @@ -0,0 +1,135 @@ +Use Alembic +=========== + +Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO. + +To add migrations to project first of all, add alembic as dependency: + +.. code-block:: console + + $ pip install --user alembic + +When you need to set up alembic for your project. + +Prepare sample project. We will have a structure: + + +.. code-block:: console + + alembic_sample/ + my_app/ + models.py + +Inside ``models.py`` define simple DB Model with GINO: + +.. code-block:: python + + from gino import Gino + + db = Gino() + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + +Set up Alembic +^^^^^^^^^^^^^^ + +This will need to be done only once. Go to the main folder of your project +``alembic_sample`` and run: + +.. code-block:: console + + $ alembic init alembic + + +Alembic will create a bunch of files and folders in your project directory. One of them +will be ``alembic.ini``. Open ``alembic.ini`` (you can find it in the main project +folder ``alembic_sample``). Now change property ``sqlalchemy.url =`` with your DB +credentials. Like this: + +.. code-block:: ini + + sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}} + + +Next go to folder ``alembic/`` and open ``env.py`` file. Inside the ``env.py`` file you +need to import the ``db`` object. In our case ``db`` object is ``db`` from ``models`` +modules. This is a variable that links to your ``Gino()`` instance. + +Inside ``alembic/env.py``:: + + from main_app.models import db + + +And change ``target_metadata =`` to:: + + target_metadata = db + +That’s it. We finished setting up Alembic for a project. + +.. note:: + + All ``alembic`` commands must be run always from the folder that contains the + ``alembic.ini`` file. + + +Create first migration revision +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema. + +.. code-block:: console + + $ alembic revision -m "first migration" --autogenerate --head head + +If you have any problems relative to package imports similar to this example: + +.. code-block:: console + + File "alembic/env.py", line 7, in + from main_app.models import db + ModuleNotFoundError: No module named 'main_app' + +Either install your project locally with ``pip install -e .``, ``poetry install`` or +``python setup.py develop``, or add you package to PYTHONPATH, like this: + +.. code-block:: console + + $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample + +After the successful run of ``alembic revision`` in folder ``alembic/versions`` you will +see a file with new migration. + + +Apply migration on DB +^^^^^^^^^^^^^^^^^^^^^ + +Now time to apply migration to DB. It will create tables based on you DB Models. + +.. code-block:: console + + $ alembic upgrade head + +Great. Now you apply your first migration. Congratulations! + +Next time, when you will make any changes in DB models just do: + +.. code-block:: console + + $ alembic revision -m "your migration description" --autogenerate --head head + +And + +.. code-block:: console + + alembic upgrade head + + +Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org diff --git a/docs/en/master/_sources/how-to/bakery.rst.txt b/docs/en/master/_sources/how-to/bakery.rst.txt new file mode 100644 index 0000000..2f30075 --- /dev/null +++ b/docs/en/master/_sources/how-to/bakery.rst.txt @@ -0,0 +1,214 @@ +Bake Queries +============ + +.. versionadded:: 1.1 + +Baked queries are used to boost execution performance for constantly-used queries. +Similar to the :doc:`orm/extensions/baked` in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to **bake them before creating the engine**. + +GINO provides two approaches for baked queries: + +1. Low-level :class:`~gino.bakery.Bakery` API +2. High-level :meth:`Gino.bake() ` integration + + +Use Bakery with Bare Engine +--------------------------- + +First, we need a bakery:: + + import gino + + bakery = gino.Bakery() + +Then, let's bake some queries:: + + db_time = bakery.bake("SELECT now()") + +Or queries with parameters:: + + user_query = bakery.bake("SELECT * FROM users WHERE id = :uid") + +Let's assume we have this ``users`` table defined in SQLAlchemy Core:: + + import sqlalchemy as sa + + metadata = sa.MetaData() + user_table = sa.Table( + "users", metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("name", sa.String), + ) + +Now we can bake a similar query with SQLAlchemy Core:: + + user_query = bakery.bake( + sa.select([user_table]).where(user.c.id == sa.bindparam("uid")) + ) + +These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:: + + engine = await gino.create_engine("postgresql://localhost/", bakery=bakery) + +By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene. + +To execute the baked queries, you could treat the :class:`~gino.bakery.BakedQuery` +instances as if they are the queries themselves, for example:: + + now = await engine.scalar(db_time) + +Pass in parameter values:: + + row = await engine.first(user_query, uid=123) + + +Use the :class:`~gino.api.Gino` Integration +-------------------------------------------- + +In a more common scenario, there will be a :class:`~gino.api.Gino` instance, which has +usually a ``bind`` set - either explicitly or by the Web framework extensions:: + + from gino import Gino + + db = Gino() + + async def main(): + async with db.with_bind("postgresql://localhost/"): + ... + +A :class:`~gino.bakery.Bakery` is automatically created in the ``db`` instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + db_time = db.bake("SELECT now()") + user_getter = db.bake(User.query.where(User.id == db.bindparam("uid"))) + +And the execution is also simplified with the same ``bind`` magic:: + + async def main(): + async with db.with_bind("postgresql://localhost/"): + print(await db_time.scalar()) + + user: User = await user_getter.first(uid=1) + print(user.name) + +To make things easier, you could even define the baked queries directly on the +model:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + @db.bake + def getter(cls): + return cls.query.where(cls.id == db.bindparam("uid")) + + @classmethod + async def get(cls, uid): + return await cls.getter.one_or_none(uid=uid) + +Here GINO treats the ``getter()`` as a :meth:`~gino.declarative.declared_attr` with +``with_table=True``, therefore it takes one positional argument ``cls`` for the ``User`` +class. + + +How to customize loaders? +------------------------- + +If possible, you could bake the additional execution options into the query:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + ) + +The :meth:`~gino.bakery.Bakery.bake` method accepts keyword arguments as execution +options to e.g. simplify the example above into:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")), + loader=User.load(comment="Added by loader."), + ) + +If the query construction is complex, :meth:`~gino.bakery.Bakery.bake` could also be +used as a decorator:: + + @db.bake + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + +Or with short execution options:: + + @db.bake(loader=User.load(comment="Added by loader.")) + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")) + +Meanwhile, it is also possible to override the loader at runtime:: + + user: User = await user_getter.load(User).first(uid=1) + print(user.name) # no more comment on user! + +.. hint:: + + This override won't affect the baked query - it's used only in this execution. + + +What APIs are available on :class:`~gino.bakery.BakedQuery`? +------------------------------------------------------------ + +:class:`~gino.bakery.BakedQuery` is a :class:`~gino.api.GinoExecutor`, so it inherited +all the APIs like :meth:`~gino.api.GinoExecutor.all`, +:meth:`~gino.api.GinoExecutor.first`, :meth:`~gino.api.GinoExecutor.one`, +:meth:`~gino.api.GinoExecutor.one_or_none`, :meth:`~gino.api.GinoExecutor.scalar`, +:meth:`~gino.api.GinoExecutor.status`, :meth:`~gino.api.GinoExecutor.load`, +:meth:`~gino.api.GinoExecutor.timeout`, etc. + +:class:`~gino.api.GinoExecutor` is actually the chained ``.gino`` helper API seen +usually in queries like this:: + + user = await User.query.where(User.id == 123).gino.first() + +So a :class:`~gino.bakery.BakedQuery` can be seen as a normal query with the ``.gino`` +suffix, plus it is directly executable. + +.. seealso:: + + Please see API document of :mod:`gino.bakery` for more information. + + +I don't want the prepared statements. +------------------------------------- + +If you don't need all the baked queries (``m``) to create prepared statements for all +the active database connections (``n``) in the beginning, you could set +``prebake=False`` in the engine initialization to prevent the default initial +``m x n`` prepare calls:: + + e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False) + +Or if you're using bind:: + + await db.set_bind("postgresql://...", prebake=False) + +This is useful when you're depending on ``db.gino.create_all()`` to create the tables, +because the prepared statements can only be created after the table creation. + +The prepared statements will then be created and cached lazily on demand. + diff --git a/docs/en/master/_sources/how-to/contributing.rst.txt b/docs/en/master/_sources/how-to/contributing.rst.txt new file mode 100644 index 0000000..e3924e2 --- /dev/null +++ b/docs/en/master/_sources/how-to/contributing.rst.txt @@ -0,0 +1,152 @@ +.. highlight:: console + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/python-gino/gino/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `gino` for local development. + +1. Fork the `gino` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/gino.git + +3. Create a branch for local development:: + + $ cd gino/ + $ git checkout -b name-of-your-bugfix-or-feature + +Now you can make your changes locally. + +4. Create virtual environment. Example for virtualenvwrapper:: + + $ mkvirtualenv gino + +5. Activate the environment and install requirements:: + + $ pip install -r requirements_dev.txt + +6. When you're done making changes, check that your changes pass syntax checks:: + + $ flake8 gino tests + +7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):: + + $ pytest tests + $ tox + +8. For docs run:: + + $ make docs + +It will build and open up docs in your browser. + +9. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +10. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.6, 3.7, 3.8 and 3.9. Check + https://github.com/python-gino/gino/actions?query=event%3Apull_request + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test -svx tests.test_gino + +By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:: + + CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino'; + CREATE DATABASE gino WITH OWNER = gino; + +Then run the tests like so:: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino + $ py.test + +Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):: + + $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem + $ openssl rsa -in privkey.pem -passin pass:abcd -out server.key + $ openssl req -x509 -in server.req -text -key server.key -out server.crt + $ chmod 600 server.key + $ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + +Terminal 2 (client):: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433 + $ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'" + $ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;" + $ pytest tests/test_aiohttp.py diff --git a/docs/en/master/_sources/how-to/crud.rst.txt b/docs/en/master/_sources/how-to/crud.rst.txt new file mode 100644 index 0000000..dd23336 --- /dev/null +++ b/docs/en/master/_sources/how-to/crud.rst.txt @@ -0,0 +1,5 @@ +==== +CRUD +==== + +**THIS IS A WIP** diff --git a/docs/en/master/_sources/how-to/extensions.rst.txt b/docs/en/master/_sources/how-to/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/en/master/_sources/how-to/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/en/master/_sources/how-to/extensions/sanic.rst.txt b/docs/en/master/_sources/how-to/extensions/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/en/master/_sources/how-to/extensions/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/en/master/_sources/how-to/extensions/starlette.rst.txt b/docs/en/master/_sources/how-to/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/en/master/_sources/how-to/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/en/master/_sources/how-to/extensions/tornado.rst.txt b/docs/en/master/_sources/how-to/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/en/master/_sources/how-to/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/en/master/_sources/how-to/faq.rst.txt b/docs/en/master/_sources/how-to/faq.rst.txt new file mode 100644 index 0000000..06f1298 --- /dev/null +++ b/docs/en/master/_sources/how-to/faq.rst.txt @@ -0,0 +1,389 @@ +Frequently Asked Questions +========================== + +SQLAlchemy 1.4 supports asyncio, what will GINO be? +--------------------------------------------------- + +Starting from 1.4, SQLAlchemy will `support asyncio +`__. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +`greenlet `__. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed. + +Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features: + +* Contextual Connections (see :doc:`../explanation/engine`) +* SQLAlchemy Core-based CRUD models +* The GINO Loader system +* Async MySQL support +* Typing support :sup:`NEW` +* `Trio `__ support :sup:`NEW` +* Execution performance :sup:`NEW` + +As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized. + + +ORM or not ORM? +--------------- + +GINO does perform the Object-Relational Mapping work under the +`Data Mapper Pattern `_, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely `non-ORM way +`__. + + +Can I use features of SQLAlchemy ORM? +------------------------------------- + +SQLAlchemy has `two parts `__: + +* SQLAlchemy Core +* SQLAlchemy ORM + +GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO. + + +How to join? +------------ + +GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know `how to write join in +SQLAlchemy `_. +Especially, `Ádám `_ made some amazing upgrades in +GINO `#113 `_ to make join easier, so +that you can use model classes directly as if they are tables in joining:: + + results = await User.join(Book).select().gino.all() + + +How to connect to database through SSL? +--------------------------------------- + +It depends on the dialect and database driver. For asyncpg, keyword arguments +on :func:`asyncpg.connect() ` are directly +available on :func:`~gino.create_engine` or :meth:`db.set_bind() +`. Therefore, enabling SSL is rather easy:: + + engine = await gino.create_engine(..., ssl=True) + + +What is aiocontextvars and what does it do? +------------------------------------------- + +It is a partial backport of the new built-in module `contextvars +`_ introduced in Python +3.7. In Python 3.6, ``aiocontextvars`` patches ``loop.create_task()`` +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2 + +If you are using Python 3.7, then ``aiocontextvars`` does nothing at all. + +.. note:: + + This answer is for GINO 0.8 and later, please check earlier versions of + this documentation if you are using GINO 0.7. + + +How to define relationships? +---------------------------- + +GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see :doc:`loaders` for more information. + + +How to define index with multiple columns? +------------------------------------------ + +:: + + class User(db.Model): + __tablename__ = 'users' + + first_name = db.Column(db.Unicode()) + last_name = db.Column(db.Unicode()) + + _name_idx = db.Index('index_on_name', 'first_name', 'last_name') + +The ``_name_idx`` is not used. + + +Is there a django admin interface for GINO? +------------------------------------------- + +Not quite yet, please follow `this discussion +`__. + + +How to use multiple databases for different users on the fly? +------------------------------------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. + +In order to use multiple databases, you would need multiple +:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with +lazy engine creation:: + + from asyncio import Future + from contextvars import ContextVar + + from fastapi import FastAPI, Request + from gino import create_engine + from gino.ext.starlette import Gino + + engines = {} + dbname = ContextVar("dbname") + + + class ContextualGino(Gino): + @property + def bind(self): + e = engines.get(dbname.get("")) + if e and e.done(): + return e.result() + else: + return self._bind + + @bind.setter + def bind(self, val): + self._bind = val + + + app = FastAPI() + db = ContextualGino(app) + + + @app.middleware("http") + async def lazy_engines(request: Request, call_next): + name = request.query_params.get("db", "postgres") + fut = engines.get(name) + if fut is None: + fut = engines[name] = Future() + try: + engine = await create_engine("postgresql://localhost/" + name) + except Exception as e: + fut.set_exception(e) + del engines[name] + raise + else: + fut.set_result(engine) + await fut + dbname.set(name) + return await call_next(request) + + + @app.get("/") + async def get(): + return dict(dbname=await db.scalar("SELECT current_database()")) + + +How to load complex query results? +---------------------------------- + +The API doc of :mod:`gino.loader` explains the available loaders, and there're a few +examples in :doc:`loaders` too. + +Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders - +:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Be ware of the :class:`tuple` in ``.gino.load((...))``. + + + +How to do bulk or batch insert / update? +----------------------------------------- + +For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:: + + new_names = ["Austin", "Ali", "Jeff", "Marissa"] + +To quickly insert the names in one query, first construct a dict with the +``{"model_key": "value"}`` format:: + + new_names_dict = [dict(name=new_name) for new_name in new_names] + >> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}] + +Finally, run an insert statement on the model:: + + await User.insert().gino.all(new_names_dict) + + +How to print the executed SQL? +------------------------------ + +GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``. +(Or ``db.set_bind(..., echo=True)``) Please see also `here +`__. + +If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`. + + +How to run ``EXISTS`` SQL? +-------------------------- + +:: + + await db.scalar(db.exists().where(User.email == email).select()) + + +How to work with Alembic? +------------------------- + +The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the +key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py`` +will do. See :doc:`alembic` for more details. + + +How to join the same table twice? +--------------------------------- + +This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +:func:`~gino.crud.CRUDModel.alias`, for example:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.ForeignKey("users.id")) + + Parent = User.alias() + query = User.outerjoin(Parent, User.parent_id == Parent.id).select() + users = await query.gino.load(User.load(parent=Parent)).all() + + +.. _raw-sql: + +How to execute raw SQL with parameters? +--------------------------------------- + +Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments:: + + query = db.text('SELECT * FROM users WHERE id = :id_val') + row = await db.first(query, id_val=1) + +You may even load the rows into model instances:: + + query = query.execution_options(loader=User) + user = await db.first(query, id_val=1) + + +Gino engine is not initialized? +------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before +this, you'll see this error: + +.. code-block:: text + + gino.exceptions.UninitializedError: Gino engine is not initialized. + +You could use either: + +* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the + bind on the :class:`~gino.api.Gino` instance. +* Use one of the Web framework extensions to set the bind for you in usually the server + start-up hook. +* Use explicit ``bind`` for each execution, for example:: + + engine = await create_engine("...") + # ... + user = await User.get(request.user_id, bind=engine) + + +How can I do SQL xxxx in GINO? +------------------------------ + +GINO uses `SQLAlchemy Core `__ queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column +attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column` +instances, you can use them in building your SQLAlchemy Core queries. + +Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above. diff --git a/docs/en/master/_sources/how-to/json-props.rst.txt b/docs/en/master/_sources/how-to/json-props.rst.txt new file mode 100644 index 0000000..45cf95d --- /dev/null +++ b/docs/en/master/_sources/how-to/json-props.rst.txt @@ -0,0 +1,157 @@ +JSON Property +============= + +GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields. + +Quick Start +----------- + +:: + + from gino import Gino + from sqlalchemy.dialects.postgresql import JSONB + + db = Gino() + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + birthday = db.DateTimeProperty() + +The ``age`` and ``birthday`` are JSON properties stored in the ``profile`` column. You +may use them the same way as a normal GINO model field:: + + u = await User.create(name="daisy", age=18) + print(u.name, u.age) # daisy 18 + +.. note:: + + ``profile`` is the default column name for all JSON properties in a model. If you + need a different column name for some JSON properties, you'll need to specify + explicitly:: + + audit_profile = db.Column(JSON, nullable=False, server_default="{}") + + access_log = db.ArrayProperty(prop_name="audit_profile") + abnormal_detected = db.BooleanProperty(prop_name="audit_profile") + +Using JSON properties in queries is supported:: + + await User.query.where(User.age > 16).gino.all() + +This is simply translated into a native JSON query like this: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16) + +Datetime type is very much the same:: + + from datetime import datetime + + await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all() + +And the generated SQL: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2 + -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0)) + +Here's a list of all the supported JSON properties: + ++----------------------------+-----------------------------+-------------+---------------+ +| JSON Property | Python type | JSON type | Database Type | ++============================+=============================+=============+===============+ +| :class:`.StringProperty` | :class:`str` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.IntegerProperty` | :class:`int` | ``number`` | ``int`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.BooleanProperty` | :class:`bool` | ``boolean`` | ``boolean`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.DateTimeProperty` | :class:`~datetime.datetime` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ObjectProperty` | :class:`dict` | ``object`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ArrayProperty` | :class:`list` | ``array`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ + + +Hooks +----- + +JSON property provides 2 instance-level hooks to customize the data:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @age.before_set + def age(self, val): + return val - 1 + + @age.after_get + def age(self, val): + return val + 1 + + u = await User.create(name="daisy", age=18) + print(u.name, u.profile, u.age) # daisy {'age': 17} 18 + +And 1 class-level hook to customize the SQLAlchemy expression of the property:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + height = db.JSONProperty() + + @height.expression + def height(cls, exp): + return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT) + + +Create Index on JSON Properties +------------------------------- + +We'll need to use :meth:`~gino.declarative.declared_attr` to wait until the model class +is initialized. The rest is very much the same as defining a usual index:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @db.declared_attr + def age_idx(cls): + return db.Index("age_idx", cls.age) + +This will lead to the SQL below executed if you run ``db.gino.create_all()``: + +.. code-block:: plpgsql + + CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER)); + +.. warning:: + + Alembic doesn't support auto-generating revisions for functional indexes yet. You'll + need to manually edit the revision. Please follow `this issue + `__ for updates. diff --git a/docs/en/master/_sources/how-to/loaders.rst.txt b/docs/en/master/_sources/how-to/loaders.rst.txt new file mode 100644 index 0000000..6d82937 --- /dev/null +++ b/docs/en/master/_sources/how-to/loaders.rst.txt @@ -0,0 +1,461 @@ +======================== +Loaders and Relationship +======================== + +Loaders are used to load database row results into objects. + +GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined. + + +Model Loader +------------ + +The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:: + + query = db.select([User]) + rows = await query.gino.all() + +In order to load rows into ``User`` objects, you can provide an execution +option ``loader`` with a new :class:`~gino.loader.ModelLoader` instance:: + + from gino.loader import ModelLoader + + query = db.select([User]) + query = query.execution_options(loader=ModelLoader(User)) + users = await query.gino.all() + +The :class:`~gino.loader.ModelLoader` would then load each database row into a +``User`` object. As this is frequently used, GINO made it a shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User.load()) + users = await query.gino.all() + +And another shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User) + users = await query.gino.all() + +.. tip:: + + ``User`` as ``loader`` is transformed into ``ModelLoader(User)`` by + :meth:`Loader.get() `, explained later in "Loader + Expression". + +And again:: + + query = db.select([User]) + users = await query.gino.load(User).all() + +This is identical to the normal CRUD query:: + + users = await User.query.gino.all() + + +Loader Expression +----------------- + +So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than :class:`~gino.loader.ModelLoader`, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders. + +Here is an example using all loaders at once:: + + uid, user, sep, cols = await db.select([User]).gino.load( + ( + User.id, + User, + '|', + lambda row, ctx: len(row), + ) + ).first() + +Let's check this piece by piece. Overall, the argument of +:meth:`~gino.api.GinoExecutor.load` is a tuple. This is interpreted into a +:class:`~gino.loader.TupleLoader`, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a :class:`~gino.loader.TupleLoader` is a tuple. + +:class:`~sqlalchemy.schema.Column` in Loader Expressions are interpreted as +:class:`~gino.loader.ColumnLoader`. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, :class:`~gino.loader.ColumnLoader` uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use :class:`~gino.loader.ColumnLoader`, +you'll have to declare columns for the query:: + + now = db.Column('time', db.DateTime()) + result = await db.first(db.text( + 'SELECT now() AT TIME ZONE \'UTC\'' + ).columns( + now, + ).gino.load( + ('now:', now) + ).query) + print(*result) # now: 2018-04-08 08:23:02.431847 + +Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +:class:`~gino.loader.ModelLoader`. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values. + +.. tip:: + + For a complex loader expression, the same row is given to all loaders, so + it doesn't matter ``User.id`` is already used before the model loader. + +The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +:func:`map`, the return value of the call will be the loaded result. + +At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the ``'|'`` separator, it will show up as the third item +in every result returned by the query. + + +Many-to-One Relationship +------------------------ + +A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + +So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:: + + async for child in Child.load(parent=Parent).gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +As you may have noticed, ``Child.load`` is exactly the shortcut to create +:class:`~gino.loader.ModelLoader` in the very first example. With some +additional keyword arguments, ``Child.load(parent=Parent)`` is still a +:class:`~gino.loader.ModelLoader` for ``Child``, the model loader is at the +same time a **query builder**. It is identical to do this:: + + async for child in Child.load(parent=Parent).query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +The :attr:`~gino.loader.Loader.query` dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The :class:`~gino.loader.Loader` simply forwarded unknown +attributes to its :attr:`~gino.loader.Loader.query`, that's why ``.query`` can +be omitted. + +For :class:`~gino.loader.ModelLoader`, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using :func:`setattr`. For example, ``Parent`` is +interpreted as ``ModelLoader(Parent)`` which loads ``Parent`` instances, and +``Parent`` instances are set as the ``parent`` attribute of the outer ``Child`` +instance. + +.. warning:: + + If multiple children references the same parent, then each child owns a + unique parent instance with identical values. + +.. tip:: + + You don't have to define ``parent`` attribute on ``Child``. But if you do, + you gain the ability to customize how parent is stored or retrieved. For + example, let's store the parent instance as ``_parent``:: + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + _parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + +The query builder works recursively. For :class:`~gino.loader.ModelLoader`, it +uses ``LEFT OUTER JOIN`` to connect the ``FROM`` clauses, in order to achieve +many-to-one scenario. The ``ON`` clause is determined automatically by foreign +keys. You can also customize the ``ON`` clause in case there is no foreign key +(a promise is a promise):: + + loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + async for child in loader.query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +And subloaders can be nested:: + + subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id)) + +By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values. + + +Self Referencing +---------------- + +.. warning:: + + Experimental feature. + +Self referencing is usually used to create a tree-like structure. For example:: + + class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('categories.id')) + +In order to load leaf categories with their parents, an alias is needed:: + + Parent = Category.alias() + +Then the query would be something like this:: + + parents = db.select([Category.parent_id]) + query = Category.load(parent=Parent.on( + Category.parent_id == Parent.id + )).where( + ~Category.id.in_(db.select([Category.alias().parent_id])) + ) + async for c in query.gino.iterate(): + print(f'Leaf: {c.id}, Parent: {c.parent.id}') + +The generated SQL looks like this: + +.. code-block:: SQL + + SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id + FROM categories LEFT OUTER JOIN categories AS categories_1 + ON categories.parent_id = categories_1.id + WHERE categories.id NOT IN ( + SELECT categories_2.parent_id + FROM categories AS categories_2 + ) + + +Other Relationships +------------------- + +GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + @children.setter + def add_child(self, child): + self._children.add(child) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child)).all() + +Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the ``add_child`` setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +``parent.add_child = new_child``. + +Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries. + +GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._child = None + + @property + def child(self): + return self._child + + @child.setter + def child(self, child): + self._child = child + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all() + + +Similarly, you can build many-to-many relationships in the same way:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + def add_child(self, child): + self._children.add(child) + child._parents.add(self) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._parents = set() + + @property + def parents(self): + return self._parents + + + class ParentXChild(db.Model): + __tablename__ = 'parents_x_children' + + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + child_id = db.Column(db.Integer, db.ForeignKey('children.id')) + + + query = Parent.outerjoin(ParentXChild).outerjoin(Child).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all() + +Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ``ParentXChild`` instances. + + +Advanced Usage of Loaders +------------------------- + +You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For `example +`_, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Using alias to get ID-ascending pairs from the same table:: + + ua1 = User.alias() + ua2 = User.alias() + join_query = select([ua1, ua2]).where(ua1.id < ua2.id) + loader = ua1.load('id'), ua2.load('id') + result = await join_query.gino.load(loader).all() + print(result) # e.g. [(1, 2), (1, 3), (2, 3)] + +Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future. diff --git a/docs/en/master/_sources/how-to/pool.rst.txt b/docs/en/master/_sources/how-to/pool.rst.txt new file mode 100644 index 0000000..4574ab1 --- /dev/null +++ b/docs/en/master/_sources/how-to/pool.rst.txt @@ -0,0 +1,27 @@ +=============== +Connection Pool +=============== + +Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +:class:`~gino.dialects.asyncpg.NullPool`), and users can define their own pools. +The base class should be :class:`~gino.dialects.base.Pool`. + +To use non-default pools in raw GINO:: + + from gino.dialects.asyncpg import NullPool + create_engine('postgresql://...', pool_class=NullPool) + +To use non-default pools in extensions (taking Sanic as an example):: + + from gino.dialects.asyncpg import NullPool + from gino.ext.sanic import Gino + + app = sanic.Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_KWARGS = dict( + pool_class=NullPool, + ) + db = Gino() + db.init_app(app) diff --git a/docs/en/master/_sources/how-to/sanic.rst.txt b/docs/en/master/_sources/how-to/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/en/master/_sources/how-to/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/en/master/_sources/how-to/schema.rst.txt b/docs/en/master/_sources/how-to/schema.rst.txt new file mode 100644 index 0000000..c57eaaa --- /dev/null +++ b/docs/en/master/_sources/how-to/schema.rst.txt @@ -0,0 +1,232 @@ +================== +Schema Declaration +================== + +There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy :class:`~sqlalchemy.schema.Table`. + + +GINO Engine +----------- + +This is the minimized way to use GINO - using only +:class:`~gino.engine.GinoEngine` (and :class:`~gino.engine.GinoConnection` +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two. + +For example, the table declaration is the same as SQLAlchemy core `tutorial +`_:: + + from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey + + metadata = MetaData() + + users = Table( + 'users', metadata, + + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + ) + + addresses = Table( + 'addresses', metadata, + + Column('id', Integer, primary_key=True), + Column('user_id', None, ForeignKey('users.id')), + Column('email_address', String, nullable=False) + ) + +.. note:: + + When using GINO Engine only, it is usually your own business to create the + tables with either :meth:`~sqlalchemy.schema.MetaData.create_all` on a + normal non-async SQLAlchemy engine, or using Alembic. However it is still + possible to be done with GINO if it had to:: + + import gino + from gino.schema import GinoSchemaVisitor + + async def main(): + engine = await gino.create_engine('postgresql://...') + await GinoSchemaVisitor(metadata).create_all(engine) + +Then, construct queries, in SQLAlchemy core too:: + + ins = users.insert().values(name='jack', fullname='Jack Jones') + +So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:: + + async def main(): + engine = await gino.create_engine('postgresql://localhost/gino') + conn = await engine.acquire() + await conn.status(ins) + print(await conn.all(users.select())) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Here :func:`~gino.create_engine` creates a :class:`~gino.engine.GinoEngine`, +then :meth:`~gino.engine.GinoEngine.acquire` checks out a +:class:`~gino.engine.GinoConnection`, and +:meth:`~gino.engine.GinoConnection.status` executes the insert and returns the +status text. This works similarly as SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execute` - they take the same parameters +but return a bit differently. There are also other similar query APIs: + +* :meth:`~gino.engine.GinoConnection.all` returns a list of + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.first` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.one` returns one + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.one_or_none` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.scalar` returns a single value, or + ``None`` +* :meth:`~gino.engine.GinoConnection.iterate` returns an asynchronous iterator + which yields :class:`~sqlalchemy.engine.RowProxy` + +Please go to their API for more information. + + +GINO Core +--------- + +In previous scenario, :class:`~gino.engine.GinoEngine` must not be set to +:attr:`metadata.bind ` because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of :class:`~sqlalchemy.schema.MetaData` as :class:`~gino.api.Gino`, +usually instantiated globally under the name of ``db``. It can be used as a +normal :class:`~sqlalchemy.schema.MetaData` still offering some conveniences: + +* It delegates most public types you can access on ``sqlalchemy`` +* It works with both normal SQLAlchemy engine and asynchronous GINO engine +* It exposes all query APIs on :class:`~gino.engine.GinoConnection` level +* It injects two ``gino`` extensions on SQLAlchemy query clauses and schema + items, allowing short inline execution like ``users.select().gino.all()`` +* It is also the entry for the third scenario, see later + +Then we can achieve previous scenario with less code like this:: + + from gino import Gino + + db = Gino() + + users = db.Table( + 'users', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('name', db.String), + db.Column('fullname', db.String), + ) + + addresses = db.Table( + 'addresses', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('user_id', None, db.ForeignKey('users.id')), + db.Column('email_address', db.String, nullable=False) + ) + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await users.insert().values( + name='jack', + fullname='Jack Jones', + ).gino.status() + print(await users.select().gino.all()) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but ``sqlalchemy`` seems +never imported. This is useful when ORM is unwanted. + +.. tip:: + + `asyncpgsa `_ does the same thing, + but in a conceptually reversed way - instead of having asyncpg work for + SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that + way too because GINO is inspired by asyncpgsa). Either way works fine, it's + just a matter of taste of whose API style to use, SQLAlchemy or asyncpg. + + +GINO ORM +-------- + +If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:: + + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + fullname = db.Column(db.String) + + + class Address(db.Model): + __tablename__ = 'addresses' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(None, db.ForeignKey('users.id')) + email_address = db.Column(db.String, nullable=False) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await User.create(name='jack', fullname='Jack Jones') + print(await User.query.gino.all()) + # Outputs: [] + +.. important:: + + The ``__tablename__`` is a mandatory field to define a concrete model. + +As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in ``db``. The ``class`` style is just +more declarative. Instead of ``users.c.name``, you can now access the column by +``User.name``. The implicitly created :class:`~sqlalchemy.schema.Table` is +available at ``User.__table__`` and ``Address.__table__``. You can use anything +that works in GINO core here. + +.. note:: + + Column names can be different as a class property and database column. + For example, name can be declared as + ``nickname = db.Column('name', db.Unicode(), default='noname')``. In this + example, ``User.nickname`` is used to access the column, while in database, + the column name is ``name``. + + What's worth mentioning is where raw SQL statements are used, or + ``TableClause`` is involved, like ``User.insert()``, the original name is + required to be used, because in this case, GINO has no knowledge about the + mappings. + +.. tip:: + + ``db.Model`` is a dynamically created parent class for your models. It is + associated with the ``db`` on initialization, therefore the table is put in + the very ``db`` when you declare your model class. + +Things become different when it comes to CRUD. You can use model level methods +to directly :meth:`~gino.crud.CRUDModel.create` a model instance, instead of +inserting a new row. Or :meth:`~gino.crud.CRUDModel.delete` a model instance +without needing to specify the where clause manually. Query returns model +instances instead of :class:`~sqlalchemy.engine.RowProxy`, and row values are +directly available as attributes on model instances. See also: +:doc:`/how-to/crud`. + +After all, :class:`~gino.engine.GinoEngine` is always in use. Next let's dig +more into it. diff --git a/docs/en/master/_sources/how-to/starlette.rst.txt b/docs/en/master/_sources/how-to/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/en/master/_sources/how-to/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/en/master/_sources/how-to/tornado.rst.txt b/docs/en/master/_sources/how-to/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/en/master/_sources/how-to/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/en/master/_sources/how-to/transaction.rst.txt b/docs/en/master/_sources/how-to/transaction.rst.txt new file mode 100644 index 0000000..558a5ae --- /dev/null +++ b/docs/en/master/_sources/how-to/transaction.rst.txt @@ -0,0 +1,116 @@ +=========== +Transaction +=========== + +It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an ``await`` will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it. + + +Basic usage +----------- + +Transactions belong to :class:`~gino.engine.GinoConnection`. The most common +way to use transactions is through an ``async with`` statement:: + + async with connection.transaction() as tx: + await connection.status('INSERT INTO mytable VALUES(1, 2, 3)') + +This guarantees a transaction is opened when entering the ``async with`` block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at :attr:`~gino.transaction.GinoTransaction.raw_transaction`, but in +most cases you don't need to touch it. + +GINO provides two convenient shortcuts to end the transaction early: + +* :meth:`tx.raise_commit() ` +* :meth:`tx.raise_rollback() ` + +They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the ``async with`` block after +:meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` is skipped. The +internal exception is inherited from :exc:`BaseException` so that normal ``try +... except Exception`` block can't trap it. This exception stops propagating at +the end of ``async with`` block, so you don't need to worry about handling it. + +Transactions can also be started on a :class:`~gino.engine.GinoEngine`:: + + async with engine.transaction() as tx: + await engine.status('INSERT INTO mytable VALUES(1, 2, 3)') + +Here a :class:`~gino.engine.GinoConnection` is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The :class:`~gino.engine.GinoConnection` instance is accessible at +:attr:`tx.connection `. Other than +that, everything else is the same. + +.. important:: + + The implicit connection is by default borrowed with ``reuse=True``. That + means using :meth:`~gino.engine.GinoEngine.transaction` of + :class:`~gino.engine.GinoEngine` within a connection context is the same as + calling :meth:`~gino.engine.GinoConnection.transaction` of the current + connection without having to reference it, no separate connection shall be + created. + +Similarly, if your :class:`~gino.api.Gino` instance has a bind, you may also do +the same on it:: + + async with db.transaction() as tx: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + + +Nested Transactions +------------------- + +Transactions can be nested, nested transaction will create a `savepoint +`_ as for +now on asyncpg. A similar example from asyncpg doc:: + + async with connection.transaction() as tx1: + await connection.status('CREATE TABLE mytab (a int)') + + # Create a nested transaction: + async with connection.transaction() as tx2: + await connection.status('INSERT INTO mytab (a) VALUES (1), (2)') + # Rollback the nested transaction: + tx2.raise_rollback() + + # Because the nested transaction was rolled back, there + # will be nothing in `mytab`. + assert await connection.all('SELECT a FROM mytab') == [] + +As you can see, the :meth:`~gino.transaction.GinoTransaction.raise_rollback` +breaks only the ``async with`` block of the specified ``tx2``, the outer +transaction ``tx1`` just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate. + +Because of the default reusing behavior, transactions on engine or ``db`` +follows the same nesting rules. Please see +:class:`~gino.transactions.GinoTransaction` for more information. + + +Manual Control +-------------- + +Other than using ``async with``, you can also manually control the +transaction:: + + tx = await connection.transaction() + try: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + await tx.commit() + except Exception: + await tx.rollback() + raise + +You can't use :meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` here, similarly it is +prohibited to use :meth:`~gino.transaction.GinoTransaction.commit` and +:meth:`~gino.transaction.GinoTransaction.rollback` in an ``async with`` block. diff --git a/docs/en/master/_sources/index.rst.txt b/docs/en/master/_sources/index.rst.txt new file mode 100644 index 0000000..1512377 --- /dev/null +++ b/docs/en/master/_sources/index.rst.txt @@ -0,0 +1,127 @@ +Welcome to GINO's documentation! +================================ + +.. image:: https://img.shields.io/pypi/v/gino?logo=python&logoColor=white&color=3E6CDE&style=flat-square + :alt: PyPI Release Version + :target: https://pypi.python.org/pypi/gino + +.. image:: https://img.shields.io/github/workflow/status/python-gino/gino/test?label=test&logo=github&color=3E6CDE&style=flat-square + :alt: GitHub Workflow Status for tests + :target: https://github.com/python-gino/gino/actions?query=workflow%3Atest + +.. image:: https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy&color=3E6CDE&style=flat-square + :alt: Codacy coverage + :target: https://app.codacy.com/gh/python-gino/gino/dashboard + +.. image:: https://img.shields.io/badge/Dependabot-active-brightgreen?logo=dependabot&color=3E6CDE&style=flat-square + :target: https://app.dependabot.com/accounts/python-gino/projects/129260 + :alt: Dependabot + + +GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy_ core for Python asyncio_. Now (early 2020) GINO supports only one +dialect asyncpg_. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _asyncpg: https://github.com/MagicStack/asyncpg + + +.. cssclass:: boxed-nav + +* .. image:: images/tutorials.svg + + :doc:`tutorials` + + Lessons for the newcomer to get started + +* .. image:: images/how-to.svg + + :doc:`how-to` + + Solve specific problems by steps + +* .. image:: images/explanation.svg + + :doc:`explanation` + + Explains the background and context + +* .. image:: images/reference.svg + + :doc:`reference` + + Describes the software as it is + + +Useful Links +------------ + +.. cssclass:: boxed-nav + +* .. image:: images/github.svg + + `Source Code `_ + + https://github.com/python-gino/gino + +* .. image:: images/community.svg + + `Community `_ + + https://gitter.im/python-gino/Lobby + +* .. image:: images/open-source.svg + + `BSD license `_ + + GINO is free software + +* .. image:: images/python.svg + + `Download `_ + + Download GINO from PyPI + + +.. cssclass:: divio + +Sections by `Divio `_. + +.. toctree:: + :caption: Tutorials + :maxdepth: 1 + :glob: + :hidden: + + tutorials + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* + +.. toctree:: + :caption: How-to Guides + :maxdepth: 1 + :glob: + :hidden: + + how-to + how-to/* + +.. toctree:: + :caption: Explanation + :maxdepth: 1 + :glob: + :hidden: + + explanation + explanation/* + +.. toctree:: + :caption: Reference + :maxdepth: 1 + :glob: + :hidden: + + reference + reference/* diff --git a/docs/en/master/_sources/reference.rst.txt b/docs/en/master/_sources/reference.rst.txt new file mode 100644 index 0000000..977912b --- /dev/null +++ b/docs/en/master/_sources/reference.rst.txt @@ -0,0 +1,7 @@ +Reference +========= + +.. toctree:: + :glob: + + reference/* diff --git a/docs/en/master/_sources/reference/api.rst.txt b/docs/en/master/_sources/reference/api.rst.txt new file mode 100644 index 0000000..23fc548 --- /dev/null +++ b/docs/en/master/_sources/reference/api.rst.txt @@ -0,0 +1,6 @@ +API Reference +============= + +.. toctree:: + + api/gino diff --git a/docs/en/master/_sources/reference/api/gino.aiocontextvars.rst.txt b/docs/en/master/_sources/reference/api/gino.aiocontextvars.rst.txt new file mode 100644 index 0000000..4728456 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.aiocontextvars.rst.txt @@ -0,0 +1,7 @@ +gino.aiocontextvars module +========================== + +.. automodule:: gino.aiocontextvars + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.api.rst.txt b/docs/en/master/_sources/reference/api/gino.api.rst.txt new file mode 100644 index 0000000..8825a74 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.api.rst.txt @@ -0,0 +1,7 @@ +gino.api module +=============== + +.. automodule:: gino.api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.bakery.rst.txt b/docs/en/master/_sources/reference/api/gino.bakery.rst.txt new file mode 100644 index 0000000..5762ab7 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.bakery.rst.txt @@ -0,0 +1,7 @@ +gino.bakery module +================== + +.. automodule:: gino.bakery + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.crud.rst.txt b/docs/en/master/_sources/reference/api/gino.crud.rst.txt new file mode 100644 index 0000000..9683c99 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.crud.rst.txt @@ -0,0 +1,7 @@ +gino.crud module +================ + +.. automodule:: gino.crud + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.declarative.rst.txt b/docs/en/master/_sources/reference/api/gino.declarative.rst.txt new file mode 100644 index 0000000..8ec0bdd --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.declarative.rst.txt @@ -0,0 +1,7 @@ +gino.declarative module +======================= + +.. automodule:: gino.declarative + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.dialects.aiomysql.rst.txt b/docs/en/master/_sources/reference/api/gino.dialects.aiomysql.rst.txt new file mode 100644 index 0000000..338ccc5 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.dialects.aiomysql.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.aiomysql module +============================= + +.. automodule:: gino.dialects.aiomysql + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.dialects.asyncpg.rst.txt b/docs/en/master/_sources/reference/api/gino.dialects.asyncpg.rst.txt new file mode 100644 index 0000000..9423016 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.dialects.asyncpg.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.asyncpg module +============================ + +.. automodule:: gino.dialects.asyncpg + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.dialects.base.rst.txt b/docs/en/master/_sources/reference/api/gino.dialects.base.rst.txt new file mode 100644 index 0000000..c512657 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.dialects.base.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.base module +========================= + +.. automodule:: gino.dialects.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.dialects.rst.txt b/docs/en/master/_sources/reference/api/gino.dialects.rst.txt new file mode 100644 index 0000000..5366ba2 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.dialects.rst.txt @@ -0,0 +1,20 @@ +gino.dialects package +===================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects.aiomysql + gino.dialects.asyncpg + gino.dialects.base + +Module contents +--------------- + +.. automodule:: gino.dialects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.engine.rst.txt b/docs/en/master/_sources/reference/api/gino.engine.rst.txt new file mode 100644 index 0000000..0075c29 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.engine.rst.txt @@ -0,0 +1,7 @@ +gino.engine module +================== + +.. automodule:: gino.engine + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.exceptions.rst.txt b/docs/en/master/_sources/reference/api/gino.exceptions.rst.txt new file mode 100644 index 0000000..d3659da --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.exceptions.rst.txt @@ -0,0 +1,7 @@ +gino.exceptions module +====================== + +.. automodule:: gino.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.ext.rst.txt b/docs/en/master/_sources/reference/api/gino.ext.rst.txt new file mode 100644 index 0000000..fe56088 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.ext.rst.txt @@ -0,0 +1,10 @@ +gino.ext package +================ + +Module contents +--------------- + +.. automodule:: gino.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.json_support.rst.txt b/docs/en/master/_sources/reference/api/gino.json_support.rst.txt new file mode 100644 index 0000000..5a7c069 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.json_support.rst.txt @@ -0,0 +1,7 @@ +gino.json\_support module +========================= + +.. automodule:: gino.json_support + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.loader.rst.txt b/docs/en/master/_sources/reference/api/gino.loader.rst.txt new file mode 100644 index 0000000..06ce463 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.loader.rst.txt @@ -0,0 +1,7 @@ +gino.loader module +================== + +.. automodule:: gino.loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.rst.txt b/docs/en/master/_sources/reference/api/gino.rst.txt new file mode 100644 index 0000000..b507d15 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.rst.txt @@ -0,0 +1,38 @@ +gino package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects + gino.ext + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.aiocontextvars + gino.api + gino.bakery + gino.crud + gino.declarative + gino.engine + gino.exceptions + gino.json_support + gino.loader + gino.schema + gino.strategies + gino.transaction + +Module contents +--------------- + +.. automodule:: gino + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.schema.rst.txt b/docs/en/master/_sources/reference/api/gino.schema.rst.txt new file mode 100644 index 0000000..36fbb62 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.schema.rst.txt @@ -0,0 +1,7 @@ +gino.schema module +================== + +.. automodule:: gino.schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.strategies.rst.txt b/docs/en/master/_sources/reference/api/gino.strategies.rst.txt new file mode 100644 index 0000000..01de55b --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.strategies.rst.txt @@ -0,0 +1,7 @@ +gino.strategies module +====================== + +.. automodule:: gino.strategies + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/api/gino.transaction.rst.txt b/docs/en/master/_sources/reference/api/gino.transaction.rst.txt new file mode 100644 index 0000000..2a28e55 --- /dev/null +++ b/docs/en/master/_sources/reference/api/gino.transaction.rst.txt @@ -0,0 +1,7 @@ +gino.transaction module +======================= + +.. automodule:: gino.transaction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/en/master/_sources/reference/authors.rst.txt b/docs/en/master/_sources/reference/authors.rst.txt new file mode 100644 index 0000000..1266f7f --- /dev/null +++ b/docs/en/master/_sources/reference/authors.rst.txt @@ -0,0 +1,49 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Fantix King + +Maintainers +----------- + +* Tony Wang + +Contributors +------------ + +* Neal Wang +* Binghan Li +* Vladimir Goncharov +* Kinware +* Kentoseth +* Ádám Barancsuk +* Sergey Kovalev +* jonahfang +* Yurii Shtrikker +* Nicolas Crocfer +* Denys Badzo +* Pavol Vargovcik +* Mykyta Holubakha +* Jekel +* Martin Zaťko +* Pascal van Kooten +* Michał Dziewulski +* Simeon J Morgan +* Julio Lacerda +* qulaz +* Jim O'Brien +* Ilaï Deutel +* Roald Storm +* Tiago Requeijo +* Olexiy + + +Special thanks to my wife Daisy and her outsourcing company `DecentFoX Studio`_, +for offering me the opportunity to build this project. We are open for global +software project outsourcing on Python, iOS and Android development. + +.. _DecentFoX Studio: https://decentfox.com/ diff --git a/docs/en/master/_sources/reference/extensions.rst.txt b/docs/en/master/_sources/reference/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/en/master/_sources/reference/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/en/master/_sources/reference/extensions/sanic.rst.txt b/docs/en/master/_sources/reference/extensions/sanic.rst.txt new file mode 100644 index 0000000..5a910b4 --- /dev/null +++ b/docs/en/master/_sources/reference/extensions/sanic.rst.txt @@ -0,0 +1,143 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_SSL +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_SSL: if not set, ``None`` +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/en/master/_sources/reference/extensions/starlette.rst.txt b/docs/en/master/_sources/reference/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/en/master/_sources/reference/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/en/master/_sources/reference/extensions/tornado.rst.txt b/docs/en/master/_sources/reference/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/en/master/_sources/reference/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/en/master/_sources/reference/history.rst.txt b/docs/en/master/_sources/reference/history.rst.txt new file mode 100644 index 0000000..e10bb4e --- /dev/null +++ b/docs/en/master/_sources/reference/history.rst.txt @@ -0,0 +1,624 @@ +======= +History +======= + +GINO 1.1 +-------- + +1.1.0 (pending) +^^^^^^^^^^^^^^^ + +* Added baked query feature (#478 #659 #667) +* Added ``Query.gino.execution_options`` shortcut (#659) +* Added ``@db.declared_attr(with_table=True)`` (#659) +* [Breaking] Empty object instead of ``None`` being returned for objects with values of all selected columns are None (#729) +* Added MySQL support (#381 #685) +* [Breaking] asyncpg is no longer installed as a dependency by default, install ``gino[pg]`` for the old behavior +* Fixed multiple referenced connection stack in newly created coroutines (#747) + + +GINO 1.0 +-------- + +Migrating to GINO 1.0 +^^^^^^^^^^^^^^^^^^^^^ + +GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras: + ++------------------------+---------------------------------+ +| Extension Module | Installation in GINO 1.0 | ++========================+=================================+ +| ``gino.ext.starlette`` | ``pip install gino[starlette]`` | ++------------------------+---------------------------------+ +| ``gino.ext.aiohttp`` | ``pip install gino[aiohttp]`` | ++------------------------+---------------------------------+ +| ``gino.ext.sanic`` | ``pip install gino[sanic]`` | ++------------------------+---------------------------------+ +| ``gino.ext.tornado`` | ``pip install gino[tornado]`` | ++------------------------+---------------------------------+ +| ``gino.ext.quart`` | ``pip install gino[quart]`` | ++------------------------+---------------------------------+ + +The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed ``gino[starlette]``:: + + from gino.ext.starlette import Gino + +GINO 1.0 switched to `Poetry `__ for package and +dependency management and started to use the +`src layout `__. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process: + +* Source files are now located under ``src`` directory. +* The src dist on PyPI does not include tests, docs and some other files due to + a limitation of Poetry. + +1.0.1 (2020-06-08) +^^^^^^^^^^^^^^^^^^ + +* Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4) +* Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672) +* Multiple JSON property fixes (#661 #662 #695) +* Fixed extension typing issue (#673 #674) +* Fixed model override behavior (#694) +* Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696) + +1.0.0 (2020-04-26) +^^^^^^^^^^^^^^^^^^ + +* Switched to Poetry for package and dependency management. +* [Breaking] Moved built-in extension modules to separate PyPI packages. +* Switched to src layout. +* Switched to black code style. +* Better documentation. +* [Breaking] ``none_as_none()`` is now always enabled. +* Added representation method for engine. +* Protected the URL instance fed to ``set_bind()`` from manipulation. +* Replaced some ``assert`` with ``AssertionError`` (#258 #655) + + +GINO 0.8 +-------- + +This is also version 1.0 release candidate. + +Migrating to GINO 0.8 +^^^^^^^^^^^^^^^^^^^^^ + +1. contextvars +"""""""""""""" + +We introduced aiocontextvars_ 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the ``enable_inherit()`` or +``disable_inherit()`` calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created **after** importing ``gino`` or ``aiocontextvars``, or the patch +won't work correctly. + +There is nothing to worry about in Python 3.7. + +2. none_as_none +""""""""""""""" + +When GINO tries to load a row with all ``NULL`` values into an instance, it +will now by default return ``None`` instead of an instance with all ``None`` +attributes. To recover the default behavior of 0.7, please specify +``none_as_none(False)`` in affected model loader. + +This is especially applicable to relationship sub-loaders - if the sub-loader +found it all ``NULL``, no instance will be set to parent instance. For +example:: + + child = await Child.load(parent=Parent).query.gino.first() + +If ``child.parent_id`` is ``NULL`` in database, then the ``child`` instance +won't be called with any ``setattr(child, 'parent', ...)`` at all. (If you need +``child.parent == None`` in this case, consider setting default value +``parent = None`` in child model.) + +Please note, it is deprecated to disable ``none_as_none``, and disabling will +be removed in GINO 1.0. + +0.8.7 (2020-04-19) +^^^^^^^^^^^^^^^^^^ + +* Improved error handling when attribute names collide (Contributed by Reskov in #637 #638) +* Fixed ``with_bind`` usability in aiohttp extension (#518) + +0.8.6 (2020-02-10) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``JSONPathType`` bind processor for asyncpg (#609) +* Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600) +* Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629) +* Added ``distinct()`` to ``Alias`` (#628) + +0.8.5 (2019-11-19) +^^^^^^^^^^^^^^^^^^ + +* Improved support for ``__tablename__`` in ``declared_attr`` (Contributed by Roald Storm in #592) + +0.8.4 (2019-11-09) +^^^^^^^^^^^^^^^^^^ + +* Better loader support for models in subqueries (#573 #585) +* Allowed ``__tablename__`` to be a ``declared_attr`` (#579 #582) +* Fixed Sanic 19.9.0 compatibility (#569) +* Added one() and one_or_none() (Contributed by Ilaï Deutel in #577) +* Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538) +* Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533) +* Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520) +* Fixed Grammar (Contributed by Simeon J Morgan in #504) + +0.8.3 (2019-06-06) +^^^^^^^^^^^^^^^^^^ + +* Fixed deprecated warnings in asyncpg dialect and aiohttp (#425) +* Customizable db attribute name in aiohttp app instance (#457) +* Added Starlette support (#486) + +0.8.2 (2019-03-07) +^^^^^^^^^^^^^^^^^^ + +* Added exception for unknown JSON properties (#406 #408) +* Supported Quart 0.7 (#411) +* Accepted kwargs for db init in extensions (#407 #427) +* Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440) +* Added NullPool (#437 #441) +* Unpinned dependency versions (#447) +* Added support for SQLAlchemy 1.3 (#433 #451) + +0.8.1 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Alias supported ``Label`` (#365) +* Docs update (#308, 4c59ad, #401 by Pascal van Kooten) +* Version requirement for SQLAlchemy is updated to ``>=1.2`` (#378 #382) +* Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) + * And all other extensions (#395) +* Supported Tornado 5 (#396, also thanks to Vladimir Goncharov) +* Fixed custom JSON/JSONB type support (#402 #403) + +(Most fixes done by Tony Wang) + +0.8.0 (2018-10-16) +^^^^^^^^^^^^^^^^^^ + +* Welcome Tony Wang to the maintenance team (#335) +* Allowed custom column names (#261 #297) +* Allowed column instance in ``model.load()`` (Contributed by Jekel in #323) +* [Breaking] Upgraded to aiocontextvars 0.2.0 (#333) +* Fixed bug that the same empty stack is shared between sub-tasks (#313 #334) +* [Breaking] Made ``none_as_none()`` the default behavior (#351) +* Bug fixes and docs update + + +GINO 0.7 +-------- + +This is also version 1.0 beta 3. + +0.7.7 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Backported fix for custom JSON/JSONB type support (#402 #403) + +0.7.6 (2018-08-26) +^^^^^^^^^^^^^^^^^^ + +* Updated library support (Contributed by Tony Wang in #275 #309) +* Added ``none_as_none()`` (#281 #282) +* Added ``ARRAY`` alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289) +* Added ``Model.lookup()`` to prevent updating whole table without primary key (#287 #288) +* Added ``DB_ECHO`` in extension options (Contributed by Mykyta Holubakha in #298) +* Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305) +* Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304) +* Fixed to raise ``UninitializedError`` if bind is ``None`` (Contributed by Tony Wang in #307 #310) + +0.7.5 (2018-07-26) +^^^^^^^^^^^^^^^^^^ + +* Added friendly error message when using abstract models by mistake (#224) +* Supported Python 3.7 (Contributed by Tony Wang in #265) +* Updated documentation +* Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280) + +0.7.4 (2018-06-10) +^^^^^^^^^^^^^^^^^^ + +* Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228) +* Added Quart support (#213) +* Fixed Tornado options parsing (#231) +* Improved coding style and test coverage + +0.7.3 (2018-05-19) +^^^^^^^^^^^^^^^^^^ + +* Fix for failing binary type (#225) + +0.7.2 (2018-05-15) +^^^^^^^^^^^^^^^^^^ + +* Added prepared statement support (#14) +* Added dsn in extension config (Contributed by Yurii Shtrikker in #215) + +0.7.1 (2018-05-03) +^^^^^^^^^^^^^^^^^^ + +* Added support for inline model constraints (Contributed by Kinware in #198) +* Added docs and tests for using SSL (#202) +* Added ``declared_attr`` (#204) +* Allowed ``ModelLoader`` passively load partial model (#216) + +0.7.0 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Added Python 3.5 support (#187) +* Added support to use ``dict`` as ident for ``Model.get`` (#192) +* Added result loader (partial relationship support) (#13) +* Added documentation on relationship and transaction (#146) + + +GINO 0.6 +-------- + +This is also version 1.0 beta 2. + +Migrating to GINO 0.6 +^^^^^^^^^^^^^^^^^^^^^ + +1. Task Local +""""""""""""" + +We created a new Python package aiocontextvars_ from previous ``local.py``. If +you made use of the task local features, you should install this package. + +Previous ``gino.enable_task_local()`` and ``gino.disable_task_local()`` are +replaced by ``aiocontextvars.enable_inherit()`` and +``aiocontextvars.disable_inherit()``. However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars_ by default offers task +local even without ``enable_inherit()``, which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars_ is installed. + +There is no ``gino.get_local()`` and ``gino.reset_local()`` relevant in +aiocontextvars_. The similar thing is ``aiocontextvars.ContextVar`` instance +through its ``get()``, ``set()`` and ``delete()`` methods. + +Previous ``gino.is_local_root()`` is now +``not aiocontextvars.Context.current().inherited``. + +2. Engine +""""""""" + +GINO 0.6 hides ``asyncpg.Pool`` behind the new SQLAlchemy-alike +``gino.GinoEngine``. Instead of doing this in 0.5:: + + async with db.create_pool('postgresql://...') as pool: + # your code here + +You should change it to this in 0.6:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +This equals to:: + + engine = await gino.create_engine('postgresql://...') + db.bind = engine + try: + # your code here + finally: + db.bind = None + await engine.close() + +Or:: + + engine = await db.set_bind('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Or even this:: + + db = await gino.Gino('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Choose whichever suits you the best. + +Obviously ``GinoEngine`` doesn't provide ``asyncpg.Pool`` methods directly any +longer, but you can get the underlying ``asyncpg.Pool`` object through +``engine.raw_pool`` property. + +``GinoPool.get_current_connection()`` is now changed to ``current_connection`` +property on ``GinoEngine`` instances to support multiple engines. + +``GinoPool.execution_option`` is gone, instead ``update_execution_options()`` +on ``GinoEngine`` instance is available. + +``GinoPool().metadata`` is gone, ``dialect`` is still available. + +``GinoPool.release()`` is removed in ``GinoEngine`` and ``Gino``, the +``release()`` method on ``GinoConnection`` object should be used instead. + +These methods exist both in 0.5 ``GinoPool`` and 0.6 ``GinoEngine``: +``close()``, ``acquire()``, ``all()``, ``first()``, ``scalar()``, ``status()``. + +3. GinoConnection +""""""""""""""""" + +Similarly, ``GinoConnection`` in 0.6 is no longer a subclass of +``asyncpg.Connection``, instead it has a ``asyncpg.Connection`` instance, +accessable through ``GinoConnection.raw_connection`` property. + +``GinoConnection.metadata`` is deleted in 0.6, while ``dialect`` remained. + +``GinoConnection.execution_options()`` is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior. + +``GinoConnection.release()`` is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +``permanent=False`` to remain its previous behavior. + +And ``all()``, ``first()``, ``scalar()``, ``status()``, ``iterate()``, +``transaction()`` remained in 0.6. + +4. Query API +"""""""""""" + +All five query APIs ``all()``, ``first()``, ``scalar()``, ``status()``, +``iterate()`` now accept the same parameters as SQLAlchemy ``execute()``, +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter ``bind`` if they did. Just use the API on the ``GinoEngine`` or +``GinoConnection`` object instead. + +5. Transaction +"""""""""""""" + +Transaction interface is rewritten. Now in 0.6, a ``GinoTransaction`` object is +provided consistently from all 3 methods:: + + async with db.transaction() as tx: + # within transaction + + async with engine.transaction() as tx: + # within transaction + + async with engine.acquire() as conn: + async with conn.transaction() as tx: + # within transaction + +And different usage with ``await``:: + + tx = await db.transaction() + try: + # within transaction + await tx.commit() + except: + await tx.rollback() + raise + +The ``GinoConnection`` object is available at ``tx.connection``, while +underlying transaction object from database driver is available at +``tx.transaction`` - for asyncpg it is an ``asyncpg.transaction.Transaction`` +object. + +0.6.6 (2018-05-18) +^^^^^^^^^^^^^^^^^^ + +* Backported a fix for failing binary type (#225) + +0.6.5 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Abandoned 0.6.4 and keep 0.6.x stable +* Backported doc for transaction + +0.6.4 (2018-04-16) +^^^^^^^^^^^^^^^^^^ + +Abandoned version, please use 0.7.0 instead. + +0.6.3 (2018-04-08) +^^^^^^^^^^^^^^^^^^ + +* Added aiohttp support +* Added support for calling ``create()`` on model instances (Contributed by Kinware in #178 #180) +* Fixed ``get()`` by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184) + +0.6.2 (2018-03-24) +^^^^^^^^^^^^^^^^^^ + +* Fixed SQLAlchemy prefetch issue (#141) +* Fixed issue that mixin class on Model not working (#174) +* Added more documentation (Thanks Olaf Conradi for reviewing) + +0.6.1 (2018-03-18) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``create`` and ``drop`` for ``Enum`` type (#160) +* A bit more documentation (#159) + +0.6.0 (2018-03-14) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] API Refactored, ``Pool`` replaced with ``Engine`` + + * New API ``Engine`` replaced asyncpg ``Pool`` (#59) + * Supported different dialects, theoretically + * Used aiocontextvars_ instead of builtin task local (#89) +* [Breaking] Fixed query API with ``multiparams`` (executemany) to return correctly (#20) +* [Breaking] The query methods no longer accept the parameter ``bind`` +* [Breaking] ``Gino`` no longer exposes ``postgresql`` types +* Added ``echo`` on engine (#142) +* Added tests to cover 80% of code +* Added ``gino`` extension on ``SchemaItem`` for ``create_all`` and so on (#76 #106) +* Added ``gino`` extension on model classes for ``create()`` or ``drop()`` +* Added ``_update_request_cls`` on ``CRUDModel`` (#147) +* Rewrote the documentation (#146) + +.. _aiocontextvars: https://github.com/fantix/aiocontextvars + + +GINO 0.5 +-------- + +This is also version 1.0 beta 1. + +0.5.8 (2018-02-14) +^^^^^^^^^^^^^^^^^^ + +* Preparing for 0.6.0 which will be a breaking release +* Fixed wrong value of ``Enum`` in creation (Contributed by Sergey Kovalev in #126) + +0.5.7 (2017-11-24) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.6. + +* Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114) +* Added ``Model.outerjoin`` + +0.5.6 (2017-11-23) +^^^^^^^^^^^^^^^^^^ + +* Changed to use unnamed statement when possible (#80 #90) +* Added more example (Contributed by Kentoseth in #109) +* Added ``Model.join`` and made ``Model`` selectable (Contributed by Ádám Barancsuk in #112 #113) + +0.5.5 (2017-10-18) +^^^^^^^^^^^^^^^^^^ + +* Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87) +* Added ability to reset local storage (#84) +* Fixed bug in JSON property update +* Added update chaining feature + +0.5.4 (2017-10-04) +^^^^^^^^^^^^^^^^^^ + +* Updated example (Contributed by Kinware in #75) +* Added ``Model.insert`` (Contributed by Neal Wang in #63) +* Fixed issue that non-lazy acquiring fails dirty (#79) + +0.5.3 (2017-09-23) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``no module named cutils`` error (Contributed by Vladimir Goncharov in #73) + +0.5.2 (2017-09-10) +^^^^^^^^^^^^^^^^^^ + +* Added missing driver name on dialect (#67) +* Fixed dialect to support native decimal type (#67) + +0.5.1 (2017-09-09) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.0. + +* Reverted the extension, back to pure Python (#60) +* Used SQLAlchemy ``RowProxy`` +* Added ``first_or_404`` +* Fixed bug that ``GinoPool`` cannot be inherited + +0.5.0 (2017-09-03) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten + + * Extracted CRUD operations + * Core operations are moved to ``dialect`` and execution context + * Removed ``guess_model``, switched to explicit execution options + * Turned ``timeout`` parameter to an execution option + * Extracted ``pool``, ``connection`` and ``api`` from ``asyncpg_delegate`` +* Added support for SQLAlchemy execution options, and a few custom options +* [Breaking] Made `Model.select` return rows by default (#39) +* Moved `get_or_404` to extensions (#38) +* Added iterator on model classes (#43) +* Added Tornado extension (Contributed by Vladimir Goncharov) +* Added `Model.to_dict` (#47) +* Added an extension module to update `asyncpg.Record` with processed results + + +Early Development Releases +-------------------------- + +Considered as alpha releases. + + +0.4.1 (2017-08-20) +^^^^^^^^^^^^^^^^^^ + +* Support ``select`` on model instance + +0.4.0 (2017-08-15) +^^^^^^^^^^^^^^^^^^ + +* Made ``get_or_404`` more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31) +* Delegated ``sqlalchemy.__all__`` (Contributed by Neal Wang in #10 #33) +* [Breaking] Rewrote JSON/JSONB support (#29) +* Added ``lazy`` parameter on ``db.acquire`` (Contributed by Binghan Li in #32) +* Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34) +* Fixed ``iterate`` API to be compatible with asyncpg (#32) +* Unified exceptions +* [Breaking] Changed ``update`` API (#29) +* Bug fixes + +0.3.0 (2017-08-07) +^^^^^^^^^^^^^^^^^^ + +* Supported ``__table_args__`` (#12) +* Introduced task local to manage connection in context (#19) +* Added ``query.gino`` extension for in-place execution +* Refreshed README (#3) +* Adopted PEP 487 (Contributed by Tony Wang in #17 #27) +* Used ``weakref`` on ``__model__`` of table and query (Contributed by Tony Wang) +* Delegated asyncpg ``timeout`` parameter (Contributed by Neal Wang in #16 #22) + +0.2.3 (2017-08-04) +^^^^^^^^^^^^^^^^^^ + +* Supported any primary key (Contributed by Tony Wang in #11) + +0.2.2 (2017-08-02) +^^^^^^^^^^^^^^^^^^ + +* Supported SQLAlchemy result processor +* Added rich support on JSON/JSONB +* Bug fixes + +0.2.1 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Added ``update`` and ``delete`` API + +0.2.0 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Changed API, no longer reuses asyncpg API + +0.1.1 (2017-07-25) +^^^^^^^^^^^^^^^^^^ + +* Added ``db.bind`` +* API changed: parameter ``conn`` renamed to optional ``bind`` +* Delegated asyncpg Pool with ``db.create_pool`` +* Internal enhancement and bug fixes + +0.1.0 (2017-07-21) +^^^^^^^^^^^^^^^^^^ + +* First release on PyPI. diff --git a/docs/en/master/_sources/tutorials.rst.txt b/docs/en/master/_sources/tutorials.rst.txt new file mode 100644 index 0000000..6b3bb98 --- /dev/null +++ b/docs/en/master/_sources/tutorials.rst.txt @@ -0,0 +1,9 @@ +Tutorials +========= + +.. toctree:: + :glob: + + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* diff --git a/docs/en/master/_sources/tutorials/announcement.rst.txt b/docs/en/master/_sources/tutorials/announcement.rst.txt new file mode 100644 index 0000000..f159e02 --- /dev/null +++ b/docs/en/master/_sources/tutorials/announcement.rst.txt @@ -0,0 +1,297 @@ +官宣:Python 异步编程再添一利器 +=============================== + + GINO 填补了国内外 asyncio ORM 领域的空白 + +随着 Tornado\ [1]_ 和 asyncio\ [2]_ 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。\ +在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +`GINO `__ 了解一下! + +.. image:: ../images/python-gino.webp + :align: center + + +GINO 是谁 +--------- + +GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义\ [3]_\ 手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,\ +你肯定要问一句“这是谁”。 + +ORM,即关系对象映射(Object-Relational Mapping\ [4]_\ ),是一类开发人员喜闻乐见的效率工具,\ +它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢? + +因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性\ +(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),\ +创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:\ +性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 ``current_user.name`` +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试? + +传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,\ +但应用到大型项目中却十分考验开发人员的平均水平。 + +所有这些问题如果再放进异步编程的环境里,那就是 O(n\ :sup:`2`) 的复杂度了——哦不,是 +O(2\ :sup:`n`)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。 + +所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,\ +我索性在 1.0 稳定版发布的前夕做个年终总结。 + + +先说“方便快捷” +-------------- + + “方便快捷”主要说的是开发效率。 + +重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,\ +开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。 + +:: + + from gino import Gino + db = Gino() + class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + +这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟? + +没有错,这就是 SQLAlchemy ORM\ [5]_ 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core\ [6]_\ (SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道\ +(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic\ [7]_\ 、各种 SQLAlchemy 的增强插件\ [8]_\ 、专业领域的 PostGIS/geoalchemy\ [9]_\ +等,GINO 全都兼容。 + +是不是十分方便、十分快捷?不止这样。 + +GINO 一站式地解决了常用 CRUD 快捷方式\ [10]_\ 、上下文管理(aiocontextvars\ [11]_\ [12]_\ )、 +数据库事务封装和嵌套\ [13]_\ 、连接池管理和懒加载\ [14]_\ 等多项便捷功能,无额外依赖关系,即装即用。 + +:: + + daisy = await User.create(name="daisy") + await daisy.update(name="Daisy").apply() + +GINO 还提供了\ [15]_\ 各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado\ [1]_\ 、\ +aiohttp\ [16]_\ 、Sanic\ [17]_\ 、FastAPI\ [18]_\ /Starlette\ [19]_\ 、Quart\ [20]_\ +什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。 + +为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa\ [21]_ 的降维打击: + +1. 最少侵入型\ [22]_\ :SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。 +2. 终身不婚型\ [23]_\ :天生厌恶“对象”,只愿定义“表”,空手接 SQL。 +3. 火力全开型\ [24]_\ :最大程度的便利,非典型异步 ORM。 + +最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、\ +一秒可读百万行的 asyncpg\ [25]_\ ,以及 uvloop\ [26]_\ (可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,\ +深受俄罗斯和乌克兰人民的爱戴。 + + +再说“简单明了” +-------------- + + Explicit is better than implicit. + + Simple is better than complex. + + –The Zen of Python, PEP 20\ [27]_ + +Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,\ +因此 GINO 的很多设计都受到了明确性的影响。 + +比如说,GINO 的 ``Model`` 是完全无状态的普通 Python 对象(POPO\ [28]_\ —— 例如前面的 ``User`` +类,它的实例 ``daisy`` 就是内存里面的常规对象,你可以用 ``daisy.name`` 访问属性,也可以用 +``daisy.name = "DAISY"`` 来修改属性,或者用 ``u = User()`` 来创建新的实例,\ +这些操作都不会访问数据库,绝对绿色环保无毒副作用。 + +等到需要操作数据库的时候,你一定会有感知的。比如执行 ``INSERT`` 要用 +``u = await User.create()``\ ,而 ``UPDATE`` 则是 +``await u.update(name="Daisy").apply()``\ 。 + +.. hint:: + + 其中,``u.update(name="Daisy")`` 与 ``u.name = "Daisy"`` 类似,\ + 都是只在内存里修改对象的属性,不同的是 ``u.update()`` 还会返回一个包含本次变更的中间结果,\ + 对其执行 ``await xxx.apply()`` 则会将这些变更应用到数据库里。 + +这里的 ``await`` 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 ``await`` +就没有数据库操作。 + +另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders\ [29]_\ 。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 ``u = await User.get(1)`` 可以直接获取到 ID 为 ``1`` 的用户对象。\ +但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。\ +加载器的用法也是很简单的,比如一个用户可能写了很多本书: + +:: + + class Book(db.Model): + __tablename__ = "books" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + author_id = db.ForeignKey("users.id") + +然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者: + +:: + + query = Book.outerjoin(User).select() + loader = Book.load(author=User) + async for book in query.gino.load(loader).iterate(): + print(book.title, "written by", book.author.name) + +很简单的一个外连接查询 ``Book.outerjoin(User)``\ ,配合一个直观的加载器 +``Book.load(author=User)``\ ,就实现了: + +1. 执行 ``SELECT * FROM books LEFT JOIN users ON ...``\ ; +2. 将数据库返回结果的每一行中,属于 ``books`` 的字段加载成一个 ``Book`` 实例; +3. 然后将该行中剩下的属于 ``users`` 的字段加载成一个 ``User`` 实例; +4. 最后将 ``User`` 实例设置到 ``Book`` 实例的 ``author`` 属性上。 + +既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,\ +精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。 + + +优势与不足 +---------- + +随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM\ [30]_\ 、ORM\ [31]_\ (是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。 + +GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候\ +不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、\ +快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,\ +可以谨慎地用于生产环境了。 + +以下是近来统计到的关于 GINO 的应用案例: + +- 笔者工作上开发的一个服务:\ https://github.com/uc-cdis/metadata-service +- 还是笔者自己写的一个工具:\ https://github.com/fantix/aintq +- 一个汇率 API 服务:\ https://exchangeratesapi.io/ + + .. image:: ../images/exchangeratesapi.webp + :align: center + +- 各种 Telegram、Discord 的 Bot。 +- ArchLinux 用户包:\ https://aur.archlinux.org/packages/python-gino/ + + .. image:: ../images/archlinux.webp + :align: center + +- https://github.com/bryanforbes/gino-stubs/ +- 俄语教程:\ https://www.youtube.com/watch?v=WW4rOnfhiQY +- 高性能模板项目:\ https://github.com/leosussan/fastapi-gino-arq-uvicorn +- 还有几个商用的,但没征得同意就不贴出来了。 + +另外,GINO 还贴心地提供了中文文档\ [32]_\ ,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!): + +.. image:: ../images/docs.webp + :align: center + +GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能\ +(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。 + + +建设社会主义 +------------ + +GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 **4888 元**\ 的 PyCharm +专业版全家桶 License 一枚\ [33]_\ (没有发票)。 + +目前急需帮助的有: + +1. 各个 Web 框架插件的维护工作需要多人认领; +2. 更多的例子和文档,以及中文、俄文的翻译; +3. MySQL 的支持。 + +以及下面这些一直需要的帮助: + +1. 用 GINO,找 bug,提建议; +2. 修 bug,做功能,提 PR; +3. 维护社区,回答问题,参与讨论; +4. 最后也是最重要的:\ `去 GitHub 上给 GINO 加一颗星星! + `__ + +.. image:: ../images/happy-hacking.png + :align: center + + +关于作者 +-------- + +87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。\ +小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,\ +期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、\ +新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。\ +业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P + +特别感谢 `Tony Wang `__ 对 GINO 项目的贡献,以及\ `所有人 +`__\ 的贡献。 + + +参考文献 +-------- + +.. [1] Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado. +.. [2] Python Software Foundation. asyncio — 异步 I/O. + https://docs.python.org/zh-cn/3/library/asyncio.html. +.. [3] 维基百科. GNU. https://zh.wikipedia.org/wiki/GNU. +.. [4] 维基百科. 对象关系映射. + `https://zh.wikipedia.org/wiki/对象关系映射 + `__. +.. [5] 维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy. +.. [6] Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. + https://docs.sqlalchemy.org/en/13/core/index.html. +.. [7] Michael Bayer. Welcome to Alembic’s documentation!. + https://alembic.sqlalchemy.org/en/latest/. +.. [8] Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. + https://github.com/dahlia/awesome-sqlalchemy. +.. [9] Ricardo GarciaSilva. Does it have postgis support?. + https://github.com/python-gino/gino/issues/627. +.. [10] Fantix King. GINO 基础教程 - 增删改查. + https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations. +.. [11] Python Software Foundation. contextvars —Context Variables. + https://docs.python.org/3/library/contextvars.html. +.. [12] Fantix King. gino/aiocontextvars.py. + https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py. +.. [13] Fantix King. 数据库事务. + https://python-gino.org/docs/zh/master/how-to/transaction.html. +.. [14] Fantix King. 引擎与连接. + https://python-gino.org/docs/zh/master/explanation/engine.html. +.. [15] GINO Community. GINO Community. https://github.com/python-gino/. +.. [16] aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/. +.. [17] Sanic Community. SanicFramework. https://sanicframework.org/. +.. [18] Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/. +.. [19] Encode OSS. Starlette. https://www.starlette.io/. +.. [20] Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/. +.. [21] Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. + https://github.com/CanopyTax/asyncpgsa. +.. [22] Fantix King. 表结构定义 -GINO 引擎. + https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine. +.. [23] Fantix King. 表结构定义 - GINO core.  + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core. +.. [24] Fantix King. 表结构定义 - GINO ORM. + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm. +.. [25] MagicStack Inc. + asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. + https://github.com/MagicStack/asyncpg. +.. [26] MagicStack Inc. uvloop -Ultra fast asyncio event loop. + https://github.com/MagicStack/uvloop. +.. [27] Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/. +.. [28] Wikipedia. Plain old Java object. + https://en.wikipedia.org/wiki/Plain_old_Java_object. +.. [29] Fantix King. 加载器与关系. + https://python-gino.org/docs/zh/master/how-to/loaders.html. +.. [30] Andrey Bondar. Tortoise ORM. https://tortoise.github.io/. +.. [31] Encode OSS. ORM - An async ORM. https://github.com/encode/orm. +.. [32] Fantix King. 欢迎来到 GINO 的文档!. + https://python-gino.org/docs/zh/master/index.html. +.. [33] JetBrains s.r.o. Open Source Licenses. + https://www.jetbrains.com/community/opensource/#support. diff --git a/docs/en/master/_sources/tutorials/fastapi.rst.txt b/docs/en/master/_sources/tutorials/fastapi.rst.txt new file mode 100644 index 0000000..aa2d582 --- /dev/null +++ b/docs/en/master/_sources/tutorials/fastapi.rst.txt @@ -0,0 +1,669 @@ +Build a FastAPI Server +====================== + +In this tutorial, we'll build a production-ready FastAPI_ server together. +The full functional example is available `here +`__. + +Our application stack will look like this: + +.. image:: ../images/gino-fastapi.svg + :align: center + + +.. _FastAPI: https://fastapi.tiangolo.com/ + + +Start a New Project +------------------- + +Instead of pip, let's use the shiny Poetry_ to manage our project. Follow the link to +`install Poetry `_, and create our new +project in an empty directory: + + +.. code-block:: console + + $ mkdir gino-fastapi-demo + $ cd gino-fastapi-demo + $ git init + $ poetry init + +Then follow the Poetry_ guide to finish the initialization - you may say "no" to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains ``gino-fastapi-demo``. + +.. _Poetry: https://python-poetry.org/ + + +Add Dependencies +---------------- + +FastAPI_ is built on top of the Starlette_ framework, so we shall use the `GINO +extension for Starlette `_. Simply run: + +.. code-block:: console + + $ poetry add gino[pg,starlette] + +Then let's add FastAPI_, together with the lightning-fast ASGI_ server Uvicorn_, and +Gunicorn_ as a production application server: + +.. code-block:: console + + $ poetry add fastapi uvicorn gunicorn + +For database migration, we'll use Alembic_. Because it uses normal DB-API_, we need +psycopg_ here too: + +.. code-block:: console + + $ poetry add alembic psycopg2 + +At last, let's add pytest_ in the development environment for testing. We also want to +add the requests_ library to use the Starlette_ |TestClient|_: + +.. code-block:: console + + $ poetry add -D pytest requests + +.. hint:: + + With the steps above, Poetry_ will automatically create a virtualenv_ for you + behind the scene, and all the dependencies are installed there. We will assume + using this for the rest of the tutorial. But you're free to create your own + virtualenv_, and Poetry_ will honor it when it's activated. + +That's all, this is my ``pyproject.toml`` created by Poetry_, yours should look similar: + +.. code-block:: toml + + [tool.poetry] + name = "gino-fastapi-demo" + version = "0.1.0" + description = "" + authors = ["Fantix King "] + + [tool.poetry.dependencies] + python = "^3.8" + gino = {version = "^1.0", extras = ["pg", "starlette"]} + fastapi = "^0.54.1" + uvicorn = "^0.11.3" + gunicorn = "^20.0.4" + alembic = "^1.4.2" + psycopg2 = "^2.8.5" + + [tool.poetry.dev-dependencies] + pytest = "^5.4.1" + requests = "^2.23.0" + + [build-system] + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" + +.. image:: ../images/gino-fastapi-poetry.svg + :align: right + +And there's also an auto-generated ``poetry.lock`` file with the frozen versions. The +directory layout should look like the diagram on the right. Now let's add the two files +to the Git repository (we will skip showing these git operations in future steps): + +.. code-block:: console + + $ git add pyproject.toml poetry.lock + $ git commit -m 'add project dependencies' + +.. _Starlette: https://www.starlette.io/ +.. _ASGI: https://asgi.readthedocs.io/ +.. _Uvicorn: https://www.uvicorn.org/ +.. _Gunicorn: https://gunicorn.org/ +.. _Alembic: https://alembic.sqlalchemy.org/ +.. _DB-API: https://www.python.org/dev/peps/pep-0249/ +.. _psycopg: https://www.psycopg.org/ +.. _pytest: https://docs.pytest.org/ +.. _virtualenv: https://virtualenv.pypa.io/ +.. _requests: https://requests.readthedocs.io/ + + +Write a Simple Server +--------------------- + +Now let's write some Python code. + +We'll create an extra ``src`` directory to include all the Python files, as demonstrated +in the diagram below. This is known as the "`src layout +`_" providing a cleaner hierarchy. + +.. image:: ../images/gino-fastapi-src.svg + :align: right + +The root Python package of our project is named as ``gino_fastapi_demo``, under which we +will create two Python modules: + +* ``asgi`` as the ASGI entry point - we'll feed it to the ASGI server +* ``main`` to initialize our server + +Here's ``main.py``:: + + from fastapi import FastAPI + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + return app + +And we'll simply instantiate our application in ``asgi.py``:: + + from .main import get_app + + app = get_app() + +Then run ``poetry install`` to link our Python package into the ``PYTHONPATH`` in +development mode. We'll be able to start a Uvicorn development server after that: + +.. code-block:: console + + $ poetry install + Installing dependencies from lock file + + No dependencies to install or update + + - Installing gino-fastapi-demo (0.1.0) + + $ poetry run uvicorn gino_fastapi_demo.asgi:app --reload + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + INFO: Started reloader process [53010] + INFO: Started server process [53015] + INFO: Waiting for application startup. + INFO: Application startup complete. + +The ``--reload`` option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server. + +.. hint:: + + As mentioned previously, if you're in your own virtualenv, the command ``poetry run + uvicorn`` can be simplified as just ``uvicorn``. + + ``poetry run`` is a convenient shortcut to run the following command in the + virtualenv managed by Poetry. + + +Add GINO Extension +------------------ + +.. image:: ../images/gino-fastapi-config.svg + :align: right + +Now let's add GINO to our server. + +First of all, we need a way to configure the database. In this tutorial, we'll use the +`configuration system `_ from Starlette_. +Add ``src/gino_fastapi_demo/config.py`` as follows:: + + from sqlalchemy.engine.url import URL, make_url + from starlette.config import Config + from starlette.datastructures import Secret + + config = Config(".env") + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + DB_DSN = config( + "DB_DSN", + cast=make_url, + default=URL( + drivername=DB_DRIVER, + username=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + database=DB_DATABASE, + ), + ) + DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1) + DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16) + DB_ECHO = config("DB_ECHO", cast=bool, default=False) + DB_SSL = config("DB_SSL", default=None) + DB_USE_CONNECTION_FOR_REQUEST = config( + "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True + ) + DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1) + DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1) + +This config file will load from environment variable first, if not found then from a +file named ``.env`` from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this: + +.. code-block:: console + + $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload + +.. image:: ../images/gino-fastapi-env.svg + :align: right + +Or set them in the file ``.env`` (this file must not be committed into Git, remember to +add it to ``.gitignore``): + +.. code-block:: bash + + DB_HOST=localhost + DB_USER=postgres + +Now it's time to create a PostgreSQL_ database and set the connection variables +correctly here. This is usually something like ``createdb yourdbname``, but it may vary +across different platforms, so we won't cover this part in this tutorial. + +.. tip:: + + Alternatively, you could also set ``DB_DSN`` to for example + ``postgresql://user:password@localhost:5432/dbname`` to override the other individual + config values like ``DB_HOST`` defined before ``DB_DSN``. + + If defined, ``DB_DSN`` always have the higher priority over the individual ones, + regardless of where they are defined - even if ``DB_HOST`` is defined in environment + variable and ``DB_DSN`` is defined in ``.env`` file, ``DB_HOST`` is still ignored. + Default value doesn't count. + +.. image:: ../images/gino-fastapi-models.svg + :align: right + +Then, create a new Python sub-package ``gino_fastapi_demo.models`` to encapsulate +database-related code, and add the code below to +``src/gino_fastapi_demo/models/__init__.py``:: + + from gino.ext.starlette import Gino + + from .. import config + + db = Gino( + dsn=config.DB_DSN, + pool_min_size=config.DB_POOL_MIN_SIZE, + pool_max_size=config.DB_POOL_MAX_SIZE, + echo=config.DB_ECHO, + ssl=config.DB_SSL, + use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST, + retry_limit=config.DB_RETRY_LIMIT, + retry_interval=config.DB_RETRY_INTERVAL, + ) + +At last, modify ``src/gino_fastapi_demo/main.py`` to install the GINO extension: + +.. code-block:: diff + + from fastapi import FastAPI + + + +from .models import db + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + + db.init_app(app) + return app + +Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database: + +.. code-block:: console + + WARNING: Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading... + INFO: Shutting down + INFO: Waiting for application shutdown. + INFO: Application shutdown complete. + INFO: Finished server process [63562] + INFO: Started server process [63563] + INFO: Waiting for application startup. + INFO: Connecting to the database: postgresql://fantix:***@localhost + INFO: Database connection pool created: + INFO: Application startup complete. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Create Models and API +--------------------- + +.. image:: ../images/gino-fastapi-models-users.svg + :align: right + +It's time to implement the API now. Let's say we are building a user management service, +through which we could add users, list users and delete users. + +First of all, we need a database table ``users`` to store the data, mapped to a GINO +model named ``User``. We shall add the model in ``gino_fastapi_demo.models.users``:: + + from . import db + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode(), default="unnamed") + +.. image:: ../images/gino-fastapi-views.svg + :align: right + +The model definition is simple enough to explain itself. + +Then we only have to use it properly in the API implementation, for which we'll create a +new Python sub-package ``gino_fastapi_demo.views``, and a new module +``gino_fastapi_demo.views.users`` as follows:: + + from fastapi import APIRouter + from pydantic import BaseModel + + from ..models.users import User + + router = APIRouter() + + @router.get("/users/{uid}") + async def get_user(uid: int): + user = await User.get_or_404(uid) + return user.to_dict() + + class UserModel(BaseModel): + name: str + + @router.post("/users") + async def add_user(user: UserModel): + rv = await User.create(nickname=user.name) + return rv.to_dict() + + @router.delete("/users/{uid}") + async def delete_user(uid: int): + user = await User.get_or_404(uid) + await user.delete() + return dict(id=uid) + + def init_app(app): + app.include_router(router) + +The |APIRouter|_ holds our new APIs locally, and ``init_app`` is used to integrate it +into our FastAPI application. Here we want some `inversion of control`_: let's make the +APIs plugable, so that we don't have to import all possible future views manually. We +shall use the `Entry Points`_ feature to load the dependencies. Add this code below to +``gino_fastapi_demo.main``:: + + import logging + from importlib.metadata import entry_points + + logger = logging.getLogger(__name__) + + def load_modules(app=None): + for ep in entry_points()["gino_fastapi_demo.modules"]: + logger.info("Loading module: %s", ep.name) + mod = ep.load() + if app: + init_app = getattr(mod, "init_app", None) + if init_app: + init_app(app) + +.. hint:: + + If you're running Python < 3.8, you'll need this `importlib-metadata backport + `_. + +And call it in our application factory: + +.. code-block:: diff + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + db.init_app(app) + + load_modules(app) + return app + +Finally, define the entry points in ``pyproject.toml`` following the `Poetry document +for plugins `_: + +.. code-block:: toml + + [tool.poetry.plugins."gino_fastapi_demo.modules"] + "users" = "gino_fastapi_demo.views.users" + +Run ``poetry install`` again to activate the entry points - you may need to restart the +Uvicorn_ development server manually, as the reloader cannot capture the changes we made +to ``pyproject.toml``. + +Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven't created the database tables. + +.. |APIRouter| replace:: ``APIRouter`` +.. _APIRouter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control +.. _Entry Points: https://docs.python.org/3/library/importlib.metadata.html#entry-points + + +Integrate with Alembic +---------------------- + +To get started with Alembic_, run this command in the project root directory: + +.. code-block:: console + + $ poetry run alembic init migrations + +.. image:: ../images/gino-fastapi-alembic.svg + :align: right + +This will generate a new directory ``migrations`` where Alembic_ will store database +migration revisions. At the same time, an ``alembic.ini`` file is created in the project +root directory. Let's simply add all of them to Git control. + +For Alembic_ to use our data models defined with GINO (and of course the database +config), we need to modify ``migrations/env.py`` to connect with the GINO instance: + +.. code-block:: diff + + # add your model's MetaData object here + # for 'autogenerate' support + # from myapp import mymodel + # target_metadata = mymodel.Base.metadata + -target_metadata = None + +from gino_fastapi_demo.config import DB_DSN + +from gino_fastapi_demo.main import db, load_modules + + + +load_modules() + +config.set_main_option("sqlalchemy.url", str(DB_DSN)) + +target_metadata = db + +Then create our first migration revision with: + +.. code-block:: console + + $ poetry run alembic revision --autogenerate -m 'add users table' + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.autogenerate.compare] Detected added table 'users' + Generating migrations/versions/32c0feba61ea_add_users_table.py ... done + +The generated revision file should roughly look like this:: + + def upgrade(): + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("nickname", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + def downgrade(): + op.drop_table("users") + +.. hint:: + + Whenever there is a change to the database schema in the future, just modify the + GINO models and run ``alembic revision --autogenerate`` again to generate new + revisions to track the change. Remember to review the revision file - you may want + to adjust it. + +Eventually, let's apply this migration, by upgrading to the latest revision: + +.. code-block:: console + + $ poetry run alembic upgrade head + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.runtime.migration] Running upgrade -> 32c0feba61ea, add users table + +Now all the APIs should be fully operational, try with the Swagger UI. + + +Write the Tests +--------------- + +In order not to break our development database with running tests, let's create a +separate database to run tests. Apply this change to ``gino_fastapi_demo.config``: + +.. code-block:: diff + + config = Config(".env") + + +TESTING = config("TESTING", cast=bool, default=False) + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + +if TESTING: + + if DB_DATABASE: + + DB_DATABASE += "_test" + + else: + + DB_DATABASE = "gino_fastapi_demo_test" + DB_DSN = config( + +.. hint:: + + You need to run ``createdb`` to actually create the database. If you have set + ``DB_DATABASE`` in ``.env`` - e.g. ``DB_DATABASE=mydb``, the name of the testing + database should be ``mydb_test``. Or else, ``gino_fastapi_demo_test``. + +Then, let's create our pytest_ fixture in ``tests/conftest.py``:: + + import pytest + from alembic.config import main + from starlette.config import environ + from starlette.testclient import TestClient + + environ["TESTING"] = "TRUE" + + @pytest.fixture + def client(): + from gino_fastapi_demo.main import db, get_app + + main(["--raiseerr", "upgrade", "head"]) + + with TestClient(get_app()) as client: + yield client + + main(["--raiseerr", "downgrade", "base"]) + +.. image:: ../images/gino-fastapi-tests.svg + :align: right + +This fixture creates all the database tables before running the test, yield a Starlette_ +|TestClient|_, and drop all the tables with all the data after the test to maintain a +clean environment for the next test. + +Here's a sample test in ``tests/test_users.py``:: + + import uuid + + def test_crud(client): + # create + nickname = str(uuid.uuid4()) + r = client.post("/users", json=dict(name=nickname)) + r.raise_for_status() + + # retrieve + url = f"/users/{r.json()['id']}" + assert client.get(url).json()["nickname"] == nickname + + # delete + client.delete(url).raise_for_status() + assert client.get(url).status_code == 404 + +Then run the test: + +.. code-block:: console + + $ poetry run pytest + =========================== test session starts =========================== + platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 + rootdir: gino-fastapi-demo + collected 1 item + + tests/test_users.py . [100%] + + ============================ 1 passed in 1.21s ============================ + +.. |TestClient| replace:: ``TestClient`` +.. _TestClient: https://www.starlette.io/testclient/ + + +Notes for Production +-------------------- + +Given the popularity of Docker/Kubernetes, we'll build a ``Dockerfile`` for our demo: + +.. code-block:: dockerfile + + FROM python:3.8-alpine as base + + FROM base as builder + RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev + RUN pip install poetry + COPY . /src/ + WORKDIR /src + RUN python -m venv /env && . /env/bin/activate && poetry install + + FROM base + RUN apk add --no-cache postgresql-libs + COPY --from=builder /env /env + COPY --from=builder /src /src + WORKDIR /src + CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"] + +In this ``Dockerfile``, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn_ with +|UvicornWorker|_ from Uvicorn_ as the worker class for best production reliability. + +Let's review what we have in the project. + +.. image:: ../images/gino-fastapi-layout.svg + :align: center + +This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live: + +* Set ``DB_RETRY_LIMIT`` to a larger number to allow staring the application server + before the database is fully ready. +* Implement the same retry logic in ``migrations/env.py`` so that Alembic_ gets the same + functionality. +* Enable ``DB_SSL`` if needed. +* Write a ``docker-compose.yml`` for other developers to get a quick taste or even use + it for development. +* Enable CI_, install ``pytest-cov`` and use ``--cov-fail-under`` to guarantee coverage. +* Integrate static code analysis tools and security/CVE checking tools. +* Automate Alembic_ upgrade properly - e.g. after new version is deployed. +* Be aware of the common security attacks like CSRF_, XSS_, etc. +* Write load tests. + +Again, the source code of the demo is available `here +`__, +and the source of this tutorial is `here +`__. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking! + +.. |UvicornWorker| replace:: ``UvicornWorker`` +.. _UvicornWorker: https://www.uvicorn.org/deployment/#gunicorn +.. _CI: https://en.wikipedia.org/wiki/Continuous_integration +.. _CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/docs/en/master/_sources/tutorials/tutorial.rst.txt b/docs/en/master/_sources/tutorials/tutorial.rst.txt new file mode 100644 index 0000000..d9527f3 --- /dev/null +++ b/docs/en/master/_sources/tutorials/tutorial.rst.txt @@ -0,0 +1,436 @@ +GINO Basics +=========== + +This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of: + +* RDBMS, especially PostgreSQL_ +* `Asynchronous programming in Python `_ + +Knowledge of SQLAlchemy_ is not required. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Introduction +------------ + +Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API. + +You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won't make your +code run faster, if not slower. Please read :doc:`/explanation/why` for more +information. + + +Installation +------------ + +To install GINO, run this command in your terminal: + +.. code-block:: console + + $ pip install gino + +This is the preferred method to install GINO, as it will always install the +most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +Alternatively, if you are using Poetry_ to manage your project dependencies, +you may want to run: + +.. code-block:: console + + $ poetry add gino + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ +.. _Poetry: https://python-poetry.org + + +Declare Models +-------------- + +First of all, we'll need a :class:`~gino.api.Gino` object, usually under the +name of ``db`` as a global variable:: + + from gino import Gino + + db = Gino() + +``db`` acts like a reference to the database, most database interactions will +go through it. + +"Model" is a basic concept in GINO, it is a Python class inherited from +:attr:`db.Model `. Each :class:`~gino.declarative.Model` +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let's declare a model:: + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + +By declaring this ``User`` class, we are actually defining a database table +named ``users``, with two columns ``id`` and ``nickname``. Note that the fixed +:attr:`~gino.declarative.Model.__tablename__` property is required. GINO +suggests singular for model names, and plural for table names. Each +:class:`db.Column ` property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to ``db`` types `here +`__ in the SQLAlchemy +documentation. + +.. note:: + + SQLAlchemy_ is a powerful ORM library for non-asynchronous programming in + Python, on top of which GINO is built. SQLAlchemy supports many popular + RDBMS including PostgreSQL and MySQL through different dialect + implementation, so that the same Python code can be compiled into different + SQL depending on the dialect you choose. GINO inherited this support too, + but for now there is only one dialect for PostgreSQL through asyncpg_. + +.. _asyncpg: https://github.com/MagicStack/asyncpg +.. _SQLAlchemy: https://www.sqlalchemy.org/ + +If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:: + + class Booking(db.Model): + __tablename__ = 'bookings' + + day = db.Column(db.Date) + booker = db.Column(db.String) + room = db.Column(db.String) + + _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey') + _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True) + _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room') + +It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +`here `__ in the +SQLAlchemy documentation. + +Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the :attr:`~gino.declarative.Model.__table_args__` attribute. In order +to e.g. define constraints in mixin classes, +:func:`~gino.declarative.declared_attr` is required. Please feel free to read +more about it in its API documentation. + + +Get Connected +------------- + +The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let's create a +PostgreSQL database for this tutorial: + +.. code-block:: console + + $ createdb gino + +Then we tell our ``db`` object to connect to this database:: + + import asyncio + + async def main(): + await db.set_bind('postgresql://localhost/gino') + + asyncio.get_event_loop().run_until_complete(main()) + +If this runs successfully, then you are connected to the newly created database. +Here ``postgresql`` indicates the database dialect to use (the default driver +is ``asyncpg``, you can explicitly specify that with ``postgresql+asyncpg://``, +or simply ``asyncpg://``), ``localhost`` is where the server is, and ``gino`` +is the name of the database. Check `here +`__ for more +information about how to compose this database URL. + +.. note:: + + Under the hood :meth:`~gino.api.Gino.set_bind` calls + :func:`~gino.create_engine` and bind the engine to this ``db`` object. GINO + engine is similar to SQLAlchemy engine, but not identical. Because GINO + engine is asynchronous, while the other is not. Please refer to the API + reference of GINO for more information. + +Now that we are connected, let's create the table in database (in the same +``main()`` method):: + + await db.gino.create_all() + +.. warning:: + + It is :meth:`db.gino.create_all `, + not :meth:`db.create_all `, because + ``db`` is inherited from SQLAlchemy :class:`~sqlalchemy.schema.MetaData`, + and :meth:`db.create_all ` is from + SQLAlchemy using non-asynchronous methods, which doesn't work with the + bound GINO engine. + + In practice :meth:`~gino.schema.GinoSchemaVisitor.create_all` is usually + not an ideal solution. To manage database schema, tool like Alembic_ is + recommended, please see how to :doc:`/how-to/alembic`. + +If you want to explicitly disconnect from the database, you can do this:: + + await db.pop_bind().close() + +Let's review the code we have so far together in one piece before moving on:: + + import asyncio + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + + async def main(): + await db.set_bind('postgresql://localhost/gino') + await db.gino.create_all() + + # further code goes here + + await db.pop_bind().close() + + + asyncio.get_event_loop().run_until_complete(main()) + +.. _Alembic: https://bitbucket.org/zzzeek/alembic + + +CRUD Operations +--------------- + +In order to operate on the database, one of GINO's core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations. + + +Create +^^^^^^ + +Let's start by creating a ``User``:: + + user = await User.create(nickname='fantix') + # This will cause GINO to execute this SQL with parameter 'fantix': + # INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname + +As mentioned previously, ``user`` object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:: + + print(f'ID: {user.id}') # 1 + print(f'Nickname: {user.nickname}') # fantix + +It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:: + + user = User(nickname='fantix') + user.nickname += ' (founder)' + await user.create() + +Retrieve +^^^^^^^^ + +To retrieve a model object from database by primary key, you can use the class +method :meth:`~gino.crud.CRUDModel.get` on the model class. Now let's retrieve +the same row:: + + user = await User.get(1) + # SQL (parameter: 1): + # SELECT users.id, users.nickname FROM users WHERE users.id = $1 + +Normal SQL queries are done through a class property +:attr:`~gino.crud.CRUDModel.query`. For example, let's retrieve all ``User`` +objects from database as a list:: + + all_users = await db.all(User.query) + # SQL: + # SELECT users.id, users.nickname FROM users + +Alternatively, you can use the ``gino`` extension on +:attr:`~gino.crud.CRUDModel.query`. This has exactly the same effect as above:: + + all_users = await User.query.gino.all() + # SQL: + # SELECT users.id, users.nickname FROM users + +.. note:: + + ``User.query`` is actually a SQLAlchemy query, with its own + non-asynchronous execution methods. GINO added this ``gino`` extension on + all executable SQLAlchemy clause objects to conveniently execute them in + the asynchronous way, so that it is even not needed to import the ``db`` + reference for execution. + +Now let's add some filters. For example, find all users with ID lower than 10:: + + founding_users = await User.query.where(User.id < 10).gino.all() + # SQL (parameter: 10): + # SELECT users.id, users.nickname FROM users WHERE users.id < $1 + +Read more `here `__ +about writing queries, because the query object is exactly from SQLAlchemy core. + +.. warning:: + + Once you get a model object, it is purely in memory and fully detached from + the database. That means, if the row is externally updated, the object + values remain unchanged. Likewise, changes made to the object won't affect + the database values. + + Also, GINO keeps no track of model objects, therefore getting the same row + twice returns two different object with identical values. Modifying one + does not magically affect the other one. + + Different than traditional ORMs, the GINO model objects are more like + objective SQL results, rather than stateful ORM objects. In order to adapt + for asynchronous programming, GINO is designed to be that simple. That's + also why GINO Is Not ORM. + +Sometimes we want to get only one object, for example getting the user by name +when logging in. There's a shortcut for this scenario:: + + user = await User.query.where(User.nickname == 'fantix').gino.first() + # SQL (parameter: 'fantix'): + # SELECT users.id, users.nickname FROM users WHERE users.nickname = $1 + +If there is no user named "fantix" in database, ``user`` will be ``None``. + +And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +:meth:`~gino.crud.CRUDModel.select` class method:: + + name = await User.select('nickname').where(User.id == 1).gino.scalar() + # SQL (parameter: 1): + # SELECT users.nickname FROM users WHERE users.id = $1 + print(name) # fantix + +Or get the count of all users:: + + population = await db.func.count(User.id).gino.scalar() + # SQL: + # SELECT count(users.id) AS count_1 FROM users + print(population) # 17 for example + + +Update +^^^^^^ + +Then let's try to make some modifications. In this example we'll mixin some +retrieve operations we just tried. :: + + # create a new user + user = await User.create(nickname='fantix') + + # get its name + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + assert name == user.nickname # they are both 'fantix' before the update + + # modification here + await user.update(nickname='daisy').apply() + # SQL (parameters: 'daisy', 1): + # UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname + print(user.nickname) # daisy + + # get its name again + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + print(name) # daisy + assert name == user.nickname # they are both 'daisy' after the update + +So :meth:`~gino.crud.CRUDModel.update` is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +:meth:`~gino.crud.UpdateRequest.apply` call makes the update happen in database. + +.. note:: + + GINO explicitly split the in-memory update and SQL update into two methods: + :meth:`~gino.crud.CRUDModel.update` and + :meth:`~gino.crud.UpdateRequest.apply`. :meth:`~gino.crud.CRUDModel.update` + will update the in-memory model object and return an + :class:`~gino.crud.UpdateRequest` object which contains all the + modifications. A following :meth:`~gino.crud.UpdateRequest.apply` on + :class:`~gino.crud.UpdateRequest` object will apply these recorded + modifications to database by executing a compiled SQL. + +.. tip:: + + :class:`~gino.crud.UpdateRequest` object has another method named + :meth:`~gino.crud.UpdateRequest.update` which works the same as the one + on model object, just that it combines the new modifications together with + the ones already recorded in current :class:`~gino.crud.UpdateRequest` + object, and it returns the same :class:`~gino.crud.UpdateRequest` object. + That means, you can chain the updates and end up with one + :meth:`~gino.crud.UpdateRequest.apply`, or make use of the + :class:`~gino.crud.UpdateRequest` object to combine several updates in a + batch. + +:meth:`~gino.crud.CRUDModel.update` on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the :meth:`~gino.crud.CRUDModel.update` on model class level, with a +bit difference:: + + await User.update.values(nickname='Founding Member ' + User.nickname).where( + User.id < 10).gino.status() + # SQL (parameter: 'Founding Member ', 10): + # UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2 + + name = await User.select('nickname').where( + User.id == 1).gino.scalar() + print(name) # Founding Member fantix + +There is no :class:`~gino.crud.UpdateRequest` here, everything is again +SQLAlchemy clause, its +`documentation `_ here for +your reference. + + +Delete +^^^^^^ + +At last. Deleting is similar to updating, but way simpler. :: + + + user = await User.create(nickname='fantix') + await user.delete() + # SQL (parameter: 1): + # DELETE FROM users WHERE users.id = $1 + print(await User.get(user.id)) # None + +.. hint:: + + Remember the model object is in memory? In the last :func:`print` + statement, even though the row is already deleted in database, the object + ``user`` still exists with its values untouched. + +Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):: + + await User.delete.where(User.id > 10).gino.status() + # SQL (parameter: 10): + # DELETE FROM users WHERE users.id > $1 + + +With basic :doc:`/how-to/crud`, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking! diff --git a/docs/en/master/_static/basic.css b/docs/en/master/_static/basic.css new file mode 100644 index 0000000..b04360d --- /dev/null +++ b/docs/en/master/_static/basic.css @@ -0,0 +1,768 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > p:first-child, +td > p:first-child { + margin-top: 0px; +} + +th > p:last-child, +td > p:last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +li > p:first-child { + margin-top: 0px; +} + +li > p:last-child { + margin-bottom: 0px; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > p:first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/en/master/_static/css/badge_only.css b/docs/en/master/_static/css/badge_only.css new file mode 100644 index 0000000..3c33cef --- /dev/null +++ b/docs/en/master/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} diff --git a/docs/en/master/_static/css/gino.css b/docs/en/master/_static/css/gino.css new file mode 100644 index 0000000..7f0f40b --- /dev/null +++ b/docs/en/master/_static/css/gino.css @@ -0,0 +1,826 @@ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: 100%; + box-sizing: border-box; + font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; +} + +body { + background-color: #F0F0F0; + font-weight: 500; +} + +a { + color: #3E6CDE; +} + +code, kbd, samp { + font-family: monospace; + background-color: #f8f8f8; + border: 1px solid #f0f0f0; + padding: 0 4px; + border-radius: 2px; +} + +.highlight .hll { + display: block; +} + +strong { + font-weight: 700; +} + +:root { + --v-primary-base: #212B73; + --v-secondary-base: #AD755C; + --v-accent-base: #EEF5FF; + --v-error: #FF5252; + --v-info: #BDC8F1; + --v-info-light: #3E6CDE; + --v-success: #4CAF50; + --v-success-light: rgba(76, 175, 80, 0.2); + --v-warning: #FFC107; + --v-warning-light: rgba(255, 193, 7, 0.2); + --v-border: #444b54; + --v-border-light: #c0cee1; +} + +nav { + background-color: rgba(33, 43, 115, 0.95); + -webkit-box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); +} + +.nav-wrapper { + padding: 4px 16px; + display: flex; + align-items: center; +} + +nav .brand-logo { + position: relative; + display: flex; + align-items: center; + left: 0; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +.breadcrumbs { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + -ms-overflow-style: none; +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs .breadcrumb, +.breadcrumbs .breadcrumb::before { + content: '>'; + font-size: 16px; + white-space: nowrap; +} + + +.btn-flat { + display: flex; + align-items: center; + font-weight: 500; + margin-left: 22px; + text-decoration: none; + color: #fff; + padding: 14px; + font-size: 14px; + -webkit-transition: .2s; + transition: .2s; +} + +.btn-flat:hover { + color: #f8d230; + text-shadow: #f8d230 0 0 5px; +} + +a.brackets:before, +span.brackets:empty, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +a.footnote-reference { + vertical-align: super; + font-size: 0.8em; +} + +.footnote dt { + float: left; +} + +div.search { + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +} + +div.search i { + position: absolute; + left: 10px; + transition: 0.3s; +} + +#search:focus + i { + color: var(--v-primary-base); +} + +#search { + flex: 0.1 1 192px; + background-color: rgba(255, 255, 255, 0.16); + border-radius: 28px; + height: 38px; + padding: 0 32px 0 42px; + margin: 0; + border: none; + transition: 0.3s; + color: rgba(255, 255, 255, 0.32); + font-size: 14px; +} + +#search:focus { + border: none; + background-color: #FFFFFF; + color: #000000; +} + +#search-results { + position: absolute; + left: 0; + right: -250px; + max-height: 61.8vh; + background-color: rgba(0, 0, 0, 0.8); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + overflow: scroll; + top: 51px; + padding: 16px; +} + +#search-results li { + float: none; + line-height: 1.5; + font-size: 14px; +} + +#search-results li a { + color: #4E7CEE; + padding: 0; +} + +#search-results li .context { + padding: 0 0 24px 24px; +} +#search-results .highlighted { + color: #f8d230; +} + +.theme-dark { + color: #FFFFFF; +} + +.spacer { + flex-grow: 1; +} + +.img { + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.sidenav { + top: 64px; + z-index: 900; + box-shadow: none; + bottom: 0; + height: auto; +} + +.col.body { + padding: 0; +} + +#main-content { + padding: 0 24px; + background-color: white; +} + +.sidenav ul li:first-of-type { + display: none; +} + +.sidenav ul ul li:first-of-type { + display: block; +} + +.sidenav .caption { + font-size: 1.2em; + color: var(--v-primary-base); + padding: 16px 32px 0 48px; + position: relative; + font-weight: 600; +} + +.sidenav .caption::before { + position: absolute; + display: block; + content: " "; + width: 20px; + height: 20px; + left: 16px; + top: 20px; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.sidenav li > a { + font-size: 14px; + line-height: 14px; + padding: 12px 24px 12px 48px; + height: auto; +} + +.sidenav li li > a { + padding-left: 70px; +} + +.sidenav .caption:nth-of-type(1)::before { + background-image: url(../images/tutorials-icon.svg); +} +.sidenav .caption:nth-of-type(2)::before { + background-image: url(../images/how-to-icon.svg); +} +.sidenav .caption:nth-of-type(3)::before { + background-image: url(../images/explanation-logo.svg); +} +.sidenav .caption:nth-of-type(4)::before { + background-image: url(../images/reference-logo.svg); +} + +.sidenav a.current { + background-color: var(--v-accent-base); +} + +.github { + margin-left: 16px; + transition: 0.3s; +} + +.github:hover { + text-shadow: #FFFFFF 0 0 5px; +} + +.github i { + font-size: 40px; +} + +.right-nav.col { + padding: 0; +} + +.table-of-contents { + padding: 16px; + top: 64px; + bottom: 0; + overflow-y: auto; +} + +.table-of-contents li { + padding: 0; +} + +.table-of-contents > ul > li > a { + display: none; +} + +.table-of-contents > ul ul > li > a.active, +.table-of-contents > ul ul > li > a:hover { + padding-left: 14px; +} + +.table-of-contents > ul ul ul > li > a.active, +.table-of-contents > ul ul ul > li > a:hover { + padding-left: 30px; +} + +.table-of-contents > ul ul ul > li > a { + padding-left: 32px; +} + +.table-of-contents > ul ul ul ul > li > a.active, +.table-of-contents > ul ul ul ul > li > a:hover { + padding-left: 46px; +} + +.table-of-contents > ul ul ul ul > li > a { + padding-left: 48px; +} + +.table-of-contents a { + height: auto; + padding-top: 2px; + padding-bottom: 2px; +} + +.table-of-contents a.active, +.table-of-contents a:hover { + color: var(--v-primary-base); + border-left: 2px solid var(--v-primary-base); + font-weight: 500; +} + +.table-of-contents #version-selector hr { + margin-top: 48px; +} + +.table-of-contents #version-selector div { + margin-top: 16px; + color: #757575; +} + +.table-of-contents #version-selector a { + color: #757575; +} + +.table-of-contents .add-your-lang, +.table-of-contents .add-your-lang a, +.table-of-contents .add-your-lang a:active, +.table-of-contents .add-your-lang a:hover { + border-left: none; + font-size: 14px; + text-decoration: underline; + text-align: right; + margin-top: 8px; + color: #aaa; + font-weight: normal; +} + +.table-of-contents #version-selector span, +.table-of-contents #version-selector a, +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + border-left: none; + padding: 8px; + margin-left: 16px; +} + +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + color: var(--v-info-light); +} + +.table-of-contents #version-selector span { + color: var(--v-info-light); +} + +.version-warning { + border: 1px solid #757575; + background-color: #ffaaaa; + padding: 8px; +} + +h1 { + font-size: 32px; + margin: 32px 0 20px 0; + font-weight: 600; +} + +h2 { + font-size: 26px; + margin: 18px 0 12px 0; + font-weight: 600; +} + +h3 { + font-size: 22px; + margin: 17px 0 11px 0; + font-weight: 600; +} + +h4 { + font-size: 18px; + margin: 14px 0 9px 0; + font-weight: 600; +} + +h5 { + font-size: 16px; + margin: 13px 0 8px 0; + font-weight: 600; +} + +h6 { + font-size: 15px; + margin: 12px 0 7px 0; + font-weight: 600; +} + +a.headerlink { + padding-left: 8px; + visibility: hidden; + position: absolute; + font-family: sans-serif; +} + +:hover > a.headerlink { + visibility: visible; +} + +.section { + /*margin-top: -64px;*/ + /*padding-top: 64px;*/ +} + +.highlight { + background-color: rgba(0, 0, 0, 0.8); + clear: both; +} + +.highlight pre { + border: 1px solid var(--v-border-light); + padding: 1em; + font-size: 14px; + overflow-x: auto; +} + +.body li p { + margin: 0; +} + +.body ul li { + list-style-type: disc; + margin-left: 2rem; +} + +.body ul li li { + list-style-type: circle; +} + +.body ul li li li { + list-style-type: square; +} + +.admonition { + border-radius: 4px; + padding: 1px 1rem 40px; + box-shadow: #D1CECE 0 0 4px; + background-repeat: no-repeat; + background-position: 100% calc(100% + 20px); + background-size: 128px; +} + +.admonition-title { + font-size: 18px; + font-weight: 600; + color: #3E6CDE; +} + +.admonition-title:before { + content: " "; + display: inline-block; + width: 20px; + height: 20px; + margin: 0 8px -4px 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.admonition.note, +.admonition.seealso { + background-color: #F5FAFD; + background-image: url(../images/hmm.png); +} + +.admonition.note .admonition-title:before, +.admonition.seealso .admonition-title:before { + background-image: url(../images/icon-note.svg); +} + +.admonition.tip, +.admonition.hint { + background-color: #F5FDF6; + background-image: url(../images/aha.png); +} + +.admonition.tip { + background-image: url(../images/OK.png); +} + +.admonition.hint .admonition-title:before { + background-image: url(../images/icon-hint.svg); +} + +.admonition.warning { + background-color: #FDF5F5; + background-image: url(../images/fighting.png); +} + +.admonition.warning .admonition-title:before { + background-image: url(../images/icon-warning.svg); +} + +.admonition.tip .admonition-title:before { + background-image: url(../images/icon-info.svg); +} + +.body .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 700px; +} + +.body .section ul.boxed-nav li { + border-radius: 4px; + box-shadow: #D1CECE 0 0 4px; + padding: 10px; + margin: 25px 0 0; + width: 340px; + list-style: none; + color: #202A73; + font-weight: 500; + overflow: hidden; +} + +.body .section ul.boxed-nav li div { + background-color: #F4F7FC; + border-radius: 4px; + height: 110px; + padding: 30px 30px 0; + background-image: url(../images/box-bg-dec.svg); + background-repeat: no-repeat; + background-position: -15px -10px; +} + +.body .section ul.boxed-nav li:nth-of-type(3n) div { + background-image: url(../images/box-bg-dec-2.svg); +} + +.body .section ul.boxed-nav li:nth-of-type(3n+1) div { + background-image: url(../images/box-bg-dec-3.svg); +} + +.body .section ul.boxed-nav li a img { + width: 42px; + margin: 0 30px 30px 0; + float: left; +} + +.body .section ul.boxed-nav li p { + font-size: 18px; + color: #212B73; + line-height: 18px; + margin-bottom: 8px; +} + +.body .section ul.boxed-nav li p:nth-of-type(2) { + font-size: 14px; + word-break: break-word; +} + +.body .section ul.boxed-nav p a, +.body .section ul.boxed-nav p a:visited { + color: #212B73; +} + +p.divio { + position: absolute; + font-size: 0.8em; + color: #aaa; + margin-top: 40px; +} + +p.divio a, p.divio a:visited { + color: #aaa; +} + +.github-stars { + display: flex; + align-items: center; + padding: 0; + margin-left: 32px; + border-radius: 27px; + font-size: 14px; + font-weight: 500; +} + +.github-stars div { + border: 1px solid #fff; + line-height: 27px; + background-color: #1E2B78; + transition: 0.1s; + height: 29px; +} + +.github-stars div.left { + border-bottom-left-radius: 27px; + border-top-left-radius: 27px; + border-right: none; + padding: 0 7px 0 34px; + position: relative; +} + +.github-stars div.left .github-logo { + position: absolute; + height: 29px; + top: -1px; + left: -1px; +} + +.github-stars div.right { + border-bottom-right-radius: 27px; + border-top-right-radius: 27px; + padding: 0 7px 0 7px; + display: flex; + align-items: center; +} + +.github-stars div.right img { + height: 14px; + margin-right: 7px; +} + +.github-stars:hover { + color: white; + text-shadow: none; + box-shadow: #ffffff 0 0 5px; +} + +.btn-large.white img { + width: 32px; + height: 32px; + margin-top: 6px; +} + +.btn-large.white div { + color: #3E6CDE; + position: absolute; + bottom: 0; + width: 56px; + line-height: 18px; + font-size: 14px; + font-weight: 600; +} + +.sidenav-overlay { + z-index: 897; +} + +nav a.sidenav-trigger { + height: 24px; + margin: 16px 16px 16px 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +nav a.sidenav-trigger hr { + width: 24px; + margin: 0; + border: 0; + background-color: #fff; + height: 2px; +} + +.fixed-action-btn { + display: none; +} + +@media (min-width: 993px) { + main { + padding-left: 300px; + } + + #main-content { + padding: 0 48px; + margin: 16px; + border-radius: 4px; + } + + .admonition { + margin: 1.5rem 2rem; + padding: 1px 3rem 40px; + } + + img.align-center { + max-width: 100%; + display: block; + margin: 0 auto; + } + + img.align-left { + max-width: 100%; + float: left; + margin: 0 2rem 1.5rem 0; + } + + img.align-right { + max-width: 100%; + float: right; + margin: 0 0 1.5rem 2rem; + } + + .table-of-contents { + width: calc(25vw - 75px); + } + + p.divio { + margin-left: -47px; + } + + .btn-flat { + margin-left: 22px; + padding: 14px; + } + + .sidenav { + top: 64px; + } + + nav a.sidenav-trigger { + display: none; + } +} + +@media (max-width: 992px) { + img.align-left, + img.align-center, + img.align-right { + display: block; + margin: 1.5rem auto; + max-width: 100%; + } + + .admonition { + overflow: scroll; + } + + #main-content { + padding-bottom: 56px; + } + + #main-content p { + overflow-x: scroll; + } + + main > .row { + margin-bottom: 0; + } + + .breadcrumbs { + display: none; + } + + .btn-flat { + margin-left: 10px; + padding: 5px; + } +} + +@media (max-width: 740px) { + #search-container { + display: none; + } +} + +@media (max-width: 600px) { + .sidenav { + top: 56px; + } + + .fixed-action-btn { + display: block; + } +} + +@media (max-width: 510px) { + .github-stars { + display: none; + } +} diff --git a/docs/en/master/_static/css/materialize.min.css b/docs/en/master/_static/css/materialize.min.css new file mode 100644 index 0000000..74b1741 --- /dev/null +++ b/docs/en/master/_static/css/materialize.min.css @@ -0,0 +1,13 @@ +/*! + * Materialize v1.0.0 (http://materializecss.com) + * Copyright 2014-2017 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/docs/en/master/_static/css/theme.css b/docs/en/master/_static/css/theme.css new file mode 100644 index 0000000..aed8cef --- /dev/null +++ b/docs/en/master/_static/css/theme.css @@ -0,0 +1,6 @@ +/* sphinx_rtd_theme version 0.4.3 | MIT license */ +/* Built 20190212 16:02 */ +*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,.rst-content code,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:.5cm}p,h2,.rst-content .toctree-wrapper p.caption,h3{orphans:3;widows:3}h2,.rst-content .toctree-wrapper p.caption,h3{page-break-after:avoid}}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content .code-block-caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.rst-content .admonition,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.7.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),url("../fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content .code-block-caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.2857142857em;text-align:center}.fa-ul{padding-left:0;margin-left:2.1428571429em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.1428571429em;width:2.1428571429em;top:.1428571429em;text-align:center}.fa-li.fa-lg{left:-1.8571428571em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.wy-menu-vertical li span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.rst-content .fa-pull-left.admonition-title,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content dl dt .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.rst-content code.download span.fa-pull-left:first-child,.fa-pull-left.icon{margin-right:.3em}.fa.fa-pull-right,.wy-menu-vertical li span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.rst-content .fa-pull-right.admonition-title,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content dl dt .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.rst-content code.download span.fa-pull-right:first-child,.fa-pull-right.icon{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.wy-menu-vertical li span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content .code-block-caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.rst-content code.download span.pull-left:first-child,.pull-left.icon{margin-right:.3em}.fa.pull-right,.wy-menu-vertical li span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content .code-block-caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.rst-content code.download span.pull-right:first-child,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-remove:before,.fa-close:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-hotel:before,.fa-bed:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-yc:before,.fa-y-combinator:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-tv:before,.fa-television:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:""}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-signing:before,.fa-sign-language:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-vcard:before,.fa-address-card:before{content:""}.fa-vcard-o:before,.fa-address-card-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content .code-block-caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content .code-block-caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .rst-content p.caption .headerlink,.rst-content p.caption a .headerlink,a .rst-content table>caption .headerlink,.rst-content table>caption a .headerlink,a .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption a .headerlink,a .rst-content tt.download span:first-child,.rst-content tt.download a span:first-child,a .rst-content code.download span:first-child,.rst-content code.download a span:first-child,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .btn span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.btn .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .rst-content p.caption .headerlink,.rst-content p.caption .btn .headerlink,.btn .rst-content table>caption .headerlink,.rst-content table>caption .btn .headerlink,.btn .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption .btn .headerlink,.btn .rst-content tt.download span:first-child,.rst-content tt.download .btn span:first-child,.btn .rst-content code.download span:first-child,.rst-content code.download .btn span:first-child,.btn .icon,.nav .fa,.nav .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand,.nav .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .rst-content p.caption .headerlink,.rst-content p.caption .nav .headerlink,.nav .rst-content table>caption .headerlink,.rst-content table>caption .nav .headerlink,.nav .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption .nav .headerlink,.nav .rst-content tt.download span:first-child,.rst-content tt.download .nav span:first-child,.nav .rst-content code.download span:first-child,.rst-content code.download .nav span:first-child,.nav .icon{display:inline}.btn .fa.fa-large,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.btn .rst-content .code-block-caption .fa-large.headerlink,.rst-content .code-block-caption .btn .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .btn span.fa-large:first-child,.btn .rst-content code.download span.fa-large:first-child,.rst-content code.download .btn span.fa-large:first-child,.btn .fa-large.icon,.nav .fa.fa-large,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.nav .rst-content .code-block-caption .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.nav .rst-content code.download span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.nav .fa-large.icon{line-height:.9em}.btn .fa.fa-spin,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.btn .rst-content .code-block-caption .fa-spin.headerlink,.rst-content .code-block-caption .btn .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .btn span.fa-spin:first-child,.btn .rst-content code.download span.fa-spin:first-child,.rst-content code.download .btn span.fa-spin:first-child,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.nav .rst-content .code-block-caption .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.nav .rst-content code.download span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.wy-menu-vertical li span.btn.toctree-expand:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.rst-content code.download span.btn:first-child:before,.btn.icon:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.rst-content code.download span.btn:first-child:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.rst-content tt.download .btn-mini span:first-child:before,.btn-mini .rst-content code.download span:first-child:before,.rst-content code.download .btn-mini span:first-child:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.rst-content .admonition{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.admonition{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo,.rst-content .wy-alert-warning.admonition{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title,.rst-content .wy-alert-warning.admonition .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.admonition{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.admonition{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.admonition{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 .3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.3576515979%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.3576515979%;width:48.821174201%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.3576515979%;width:31.7615656014%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type="datetime-local"]{padding:.34375em .625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type="radio"][disabled],input[type="checkbox"][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{position:absolute;content:"";display:block;left:0;top:0;width:36px;height:12px;border-radius:4px;background:#ccc;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{position:absolute;content:"";display:block;width:18px;height:18px;border-radius:4px;background:#999;left:-3px;top:-3px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27AE60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:.3em;display:block}.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,.rst-content .toctree-wrapper p.caption,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2,.rst-content .toctree-wrapper p.caption{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt,.rst-content code{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:before,.wy-breadcrumbs:after{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs li code,.wy-breadcrumbs li .rst-content tt,.rst-content .wy-breadcrumbs li tt{padding:5px;border:none;background:none}.wy-breadcrumbs li code.literal,.wy-breadcrumbs li .rst-content tt.literal,.rst-content .wy-breadcrumbs li tt.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#3a7ca8;height:32px;display:inline-block;line-height:32px;padding:0 1.618em;margin:12px 0 0 0;display:block;font-weight:bold;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li code,.wy-menu-vertical li .rst-content tt,.rst-content .wy-menu-vertical li tt{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.on a:hover span.toctree-expand,.wy-menu-vertical li.current>a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a{color:#404040}.wy-menu-vertical li.toctree-l1.current li.toctree-l2>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>ul{display:none}.wy-menu-vertical li.toctree-l1.current li.toctree-l2.current>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>ul{display:block}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{display:block;background:#c9c9c9;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3{font-size:.9em}.wy-menu-vertical li.toctree-l3.current>a{background:#bdbdbd;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{display:block;background:#bdbdbd;padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980B9;text-align:center;padding:.809em;display:block;color:#fcfcfc;margin-bottom:.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-side-nav-search>a img.logo,.wy-side-nav-search .wy-dropdown>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search>a.icon img.logo,.wy-side-nav-search .wy-dropdown>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:normal;color:rgba(255,255,255,0.3)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:gray}footer p{margin-bottom:12px}footer span.commit code,footer span.commit .rst-content tt,.rst-content footer span.commit tt{padding:0px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;font-size:1em;background:none;border:none;color:gray}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{width:100%}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:before,.rst-breadcrumbs-buttons:after{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-side-scroll{width:auto}.wy-side-nav-search{width:auto}.wy-menu.wy-menu-vertical{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1100px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px 12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;display:block;overflow:auto}.rst-content pre.literal-block,.rst-content div[class^='highlight']{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px 0}.rst-content pre.literal-block div[class^='highlight'],.rst-content div[class^='highlight'] div[class^='highlight']{padding:0px;border:none;margin:0}.rst-content div[class^='highlight'] td.code{width:100%}.rst-content .linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;display:block;overflow:auto}.rst-content div[class^='highlight'] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content pre.literal-block,.rst-content div[class^='highlight'] pre,.rst-content .linenodiv pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;font-size:12px;line-height:1.4}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^='highlight'],.rst-content div[class^='highlight'] pre{white-space:pre-wrap}}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last,.rst-content .admonition .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .section ol p:last-child,.rst-content .section ul p:last-child{margin-bottom:24px}.rst-content .line-block{margin-left:0px;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content .toctree-wrapper p.caption .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content .code-block-caption .headerlink{visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content .toctree-wrapper p.caption .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after,.rst-content .code-block-caption .headerlink:after{content:"";font-family:FontAwesome}.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content .toctree-wrapper p.caption:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after,.rst-content .code-block-caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:baseline;position:relative;top:-0.4em;line-height:0;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:gray}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.docutils.citation tt,.rst-content table.docutils.citation code,.rst-content table.docutils.footnote tt,.rst-content table.docutils.footnote code{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}.rst-content table.docutils td .last,.rst-content table.docutils td .last :last-child{margin-bottom:0}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content tt,.rst-content tt,.rst-content code{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;padding:2px 5px}.rst-content tt big,.rst-content tt em,.rst-content tt big,.rst-content code big,.rst-content tt em,.rst-content code em{font-size:100% !important;line-height:normal}.rst-content tt.literal,.rst-content tt.literal,.rst-content code.literal{color:#E74C3C}.rst-content tt.xref,a .rst-content tt,.rst-content tt.xref,.rst-content code.xref,a .rst-content tt,a .rst-content code{font-weight:bold;color:#404040}.rst-content pre,.rst-content kbd,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace}.rst-content a tt,.rst-content a tt,.rst-content a code{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold;margin-bottom:12px}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:#555}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) code{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) code.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}.rst-content tt.download,.rst-content code.download{background:inherit;padding:inherit;font-weight:normal;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content tt.download span:first-child,.rst-content code.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-regular.eot");src:url("../fonts/Lato/lato-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-regular.woff2") format("woff2"),url("../fonts/Lato/lato-regular.woff") format("woff"),url("../fonts/Lato/lato-regular.ttf") format("truetype");font-weight:400;font-style:normal}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-bold.eot");src:url("../fonts/Lato/lato-bold.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-bold.woff2") format("woff2"),url("../fonts/Lato/lato-bold.woff") format("woff"),url("../fonts/Lato/lato-bold.ttf") format("truetype");font-weight:700;font-style:normal}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-bolditalic.eot");src:url("../fonts/Lato/lato-bolditalic.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-bolditalic.woff2") format("woff2"),url("../fonts/Lato/lato-bolditalic.woff") format("woff"),url("../fonts/Lato/lato-bolditalic.ttf") format("truetype");font-weight:700;font-style:italic}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-italic.eot");src:url("../fonts/Lato/lato-italic.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-italic.woff2") format("woff2"),url("../fonts/Lato/lato-italic.woff") format("woff"),url("../fonts/Lato/lato-italic.ttf") format("truetype");font-weight:400;font-style:italic}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:400;src:url("../fonts/RobotoSlab/roboto-slab.eot");src:url("../fonts/RobotoSlab/roboto-slab-v7-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.woff2") format("woff2"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.woff") format("woff"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.ttf") format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:700;src:url("../fonts/RobotoSlab/roboto-slab-v7-bold.eot");src:url("../fonts/RobotoSlab/roboto-slab-v7-bold.eot?#iefix") format("embedded-opentype"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.woff2") format("woff2"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.woff") format("woff"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.ttf") format("truetype")} diff --git a/docs/en/master/_static/doctools.js b/docs/en/master/_static/doctools.js new file mode 100644 index 0000000..b33f87f --- /dev/null +++ b/docs/en/master/_static/doctools.js @@ -0,0 +1,314 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/en/master/_static/documentation_options.js b/docs/en/master/_static/documentation_options.js new file mode 100644 index 0000000..7dfb3ac --- /dev/null +++ b/docs/en/master/_static/documentation_options.js @@ -0,0 +1,11 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.0.0a0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/en/master/_static/favicon.ico b/docs/en/master/_static/favicon.ico new file mode 100644 index 0000000..07b38bc Binary files /dev/null and b/docs/en/master/_static/favicon.ico differ diff --git a/docs/en/master/_static/file.png b/docs/en/master/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/docs/en/master/_static/file.png differ diff --git a/docs/en/master/_static/fonts/Inconsolata-Bold.ttf b/docs/en/master/_static/fonts/Inconsolata-Bold.ttf new file mode 100644 index 0000000..809c1f5 Binary files /dev/null and b/docs/en/master/_static/fonts/Inconsolata-Bold.ttf differ diff --git a/docs/en/master/_static/fonts/Inconsolata-Regular.ttf b/docs/en/master/_static/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000..fc981ce Binary files /dev/null and b/docs/en/master/_static/fonts/Inconsolata-Regular.ttf differ diff --git a/docs/en/master/_static/fonts/Inconsolata.ttf b/docs/en/master/_static/fonts/Inconsolata.ttf new file mode 100644 index 0000000..4b8a36d Binary files /dev/null and b/docs/en/master/_static/fonts/Inconsolata.ttf differ diff --git a/docs/en/master/_static/fonts/Lato-Bold.ttf b/docs/en/master/_static/fonts/Lato-Bold.ttf new file mode 100644 index 0000000..1d23c70 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato-Bold.ttf differ diff --git a/docs/en/master/_static/fonts/Lato-Regular.ttf b/docs/en/master/_static/fonts/Lato-Regular.ttf new file mode 100644 index 0000000..0f3d0f8 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato-Regular.ttf differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bold.eot b/docs/en/master/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 0000000..3361183 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bold.ttf b/docs/en/master/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 0000000..29f691d Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bold.woff b/docs/en/master/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bold.woff2 b/docs/en/master/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bolditalic.eot b/docs/en/master/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 0000000..3d41549 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bolditalic.ttf b/docs/en/master/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 0000000..f402040 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bolditalic.woff b/docs/en/master/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/en/master/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/en/master/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/en/master/_static/fonts/Lato/lato-italic.eot b/docs/en/master/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 0000000..3f82642 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/en/master/_static/fonts/Lato/lato-italic.ttf b/docs/en/master/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 0000000..b4bfc9b Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/en/master/_static/fonts/Lato/lato-italic.woff b/docs/en/master/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/en/master/_static/fonts/Lato/lato-italic.woff2 b/docs/en/master/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/en/master/_static/fonts/Lato/lato-regular.eot b/docs/en/master/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 0000000..11e3f2a Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/en/master/_static/fonts/Lato/lato-regular.ttf b/docs/en/master/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 0000000..74decd9 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/en/master/_static/fonts/Lato/lato-regular.woff b/docs/en/master/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/en/master/_static/fonts/Lato/lato-regular.woff2 b/docs/en/master/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/docs/en/master/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/en/master/_static/fonts/RobotoSlab-Bold.ttf b/docs/en/master/_static/fonts/RobotoSlab-Bold.ttf new file mode 100644 index 0000000..df5d1df Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab-Bold.ttf differ diff --git a/docs/en/master/_static/fonts/RobotoSlab-Regular.ttf b/docs/en/master/_static/fonts/RobotoSlab-Regular.ttf new file mode 100644 index 0000000..eb52a79 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab-Regular.ttf differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 0000000..79dc8ef Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 0000000..df5d1df Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 0000000..2f7ca78 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 0000000..eb52a79 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/docs/en/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/en/master/_static/fonts/fontawesome-webfont.eot b/docs/en/master/_static/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/docs/en/master/_static/fonts/fontawesome-webfont.eot differ diff --git a/docs/en/master/_static/fonts/fontawesome-webfont.svg b/docs/en/master/_static/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/docs/en/master/_static/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/master/_static/fonts/fontawesome-webfont.ttf b/docs/en/master/_static/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/docs/en/master/_static/fonts/fontawesome-webfont.ttf differ diff --git a/docs/en/master/_static/fonts/fontawesome-webfont.woff b/docs/en/master/_static/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/docs/en/master/_static/fonts/fontawesome-webfont.woff differ diff --git a/docs/en/master/_static/fonts/fontawesome-webfont.woff2 b/docs/en/master/_static/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/docs/en/master/_static/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/en/master/_static/gino.css b/docs/en/master/_static/gino.css new file mode 100644 index 0000000..22879a4 --- /dev/null +++ b/docs/en/master/_static/gino.css @@ -0,0 +1,109 @@ + +.rst-content .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.rst-content .section ul.boxed-nav li { + border-radius: 16px; + background-color: rgba(0, 0, 128, 0.05); + padding: 16px; + margin: 32px 0 0; + width: 330px; + list-style: none; + display: flex; + flex-direction: column; + align-items: center; + color: #777; +} + +.rst-content .section ul.boxed-nav p:first-of-type { + margin-top: 5px; + font-size: 1.3em; + color: #000; +} + +.rst-content .section ul.boxed-nav li a, +.rst-content .section ul.boxed-nav li a:visited { + color: #777; +} + +.rst-content .section ul.boxed-nav p:first-of-type a, +.rst-content .section ul.boxed-nav p:first-of-type a:visited { + color: #000; +} + +.rst-content .section ul.boxed-nav li:nth-of-type(5), +.rst-content .section ul.boxed-nav li:nth-of-type(6), +.rst-content .section ul.boxed-nav li:nth-of-type(7), +.rst-content .section ul.boxed-nav li:nth-of-type(8) { + background-color: rgba(128, 0, 0, 0.05); +} + +p.icons8 { + position: absolute; + padding-top: 32px; + font-size: 0.8em; + color: #aaa; +} + +p.icons8 a, p.icons8 a:visited { + color: #aaa; +} + +@media (min-width: 1200px) { + div.document { + position: relative; + } + + .contents { + position: fixed; + overflow-y: auto; + left: 1100px; + top: 0; + width: calc(100vw - 1100px); + bottom: 0; + padding: 0 1.618em; + } + + .contents a, .contents a:visited { + color: #2980B9; + } + + .contents p.topic-title { + margin-top: calc(49px + 29px + 1.618em); + } + + .rst-content .section .contents > ul > li { + list-style: none; + margin: 0; + } + + .contents > ul > li > p { + display: none; + } + + .rst-content .section .contents ul p { + margin: 0; + } + + .rst-content .section .contents ul li li { + list-style: disc; + } + + .rst-content .section .contents ul li li li { + list-style: circle; + } + + .rst-content .section .contents ul li li li li { + list-style: square; + } +} + +@media (max-width: 1300px) { + .contents { + display: none; + } +} diff --git a/docs/en/master/_static/images/OK.png b/docs/en/master/_static/images/OK.png new file mode 100644 index 0000000..81f5433 Binary files /dev/null and b/docs/en/master/_static/images/OK.png differ diff --git a/docs/en/master/_static/images/aha.png b/docs/en/master/_static/images/aha.png new file mode 100644 index 0000000..a421799 Binary files /dev/null and b/docs/en/master/_static/images/aha.png differ diff --git a/docs/en/master/_static/images/box-bg-dec-2.svg b/docs/en/master/_static/images/box-bg-dec-2.svg new file mode 100644 index 0000000..0c1e246 --- /dev/null +++ b/docs/en/master/_static/images/box-bg-dec-2.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/en/master/_static/images/box-bg-dec-3.svg b/docs/en/master/_static/images/box-bg-dec-3.svg new file mode 100644 index 0000000..0d757ca --- /dev/null +++ b/docs/en/master/_static/images/box-bg-dec-3.svg @@ -0,0 +1,25 @@ + + + + Path 10 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/master/_static/images/box-bg-dec.svg b/docs/en/master/_static/images/box-bg-dec.svg new file mode 100644 index 0000000..a6e38d5 --- /dev/null +++ b/docs/en/master/_static/images/box-bg-dec.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/en/master/_static/images/explanation-logo.svg b/docs/en/master/_static/images/explanation-logo.svg new file mode 100644 index 0000000..98df7c3 --- /dev/null +++ b/docs/en/master/_static/images/explanation-logo.svg @@ -0,0 +1,20 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/fighting.png b/docs/en/master/_static/images/fighting.png new file mode 100644 index 0000000..a7f6ddd Binary files /dev/null and b/docs/en/master/_static/images/fighting.png differ diff --git a/docs/en/master/_static/images/hmm.png b/docs/en/master/_static/images/hmm.png new file mode 100644 index 0000000..8b60eb6 Binary files /dev/null and b/docs/en/master/_static/images/hmm.png differ diff --git a/docs/en/master/_static/images/how-to-icon.svg b/docs/en/master/_static/images/how-to-icon.svg new file mode 100644 index 0000000..6506741 --- /dev/null +++ b/docs/en/master/_static/images/how-to-icon.svg @@ -0,0 +1,24 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/icon-hint.svg b/docs/en/master/_static/images/icon-hint.svg new file mode 100644 index 0000000..db86c44 --- /dev/null +++ b/docs/en/master/_static/images/icon-hint.svg @@ -0,0 +1,27 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/icon-info.svg b/docs/en/master/_static/images/icon-info.svg new file mode 100644 index 0000000..c4690a0 --- /dev/null +++ b/docs/en/master/_static/images/icon-info.svg @@ -0,0 +1,27 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/icon-note.svg b/docs/en/master/_static/images/icon-note.svg new file mode 100644 index 0000000..03308b0 --- /dev/null +++ b/docs/en/master/_static/images/icon-note.svg @@ -0,0 +1,21 @@ + + + + 编组 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/icon-warning.svg b/docs/en/master/_static/images/icon-warning.svg new file mode 100644 index 0000000..4e3ea02 --- /dev/null +++ b/docs/en/master/_static/images/icon-warning.svg @@ -0,0 +1,29 @@ + + + + 编组 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/language.svg b/docs/en/master/_static/images/language.svg new file mode 100644 index 0000000..b61c7d4 --- /dev/null +++ b/docs/en/master/_static/images/language.svg @@ -0,0 +1,16 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/reference-logo.svg b/docs/en/master/_static/images/reference-logo.svg new file mode 100644 index 0000000..b79d156 --- /dev/null +++ b/docs/en/master/_static/images/reference-logo.svg @@ -0,0 +1,22 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/images/tutorials-icon.svg b/docs/en/master/_static/images/tutorials-icon.svg new file mode 100644 index 0000000..ff76008 --- /dev/null +++ b/docs/en/master/_static/images/tutorials-icon.svg @@ -0,0 +1,21 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/_static/jquery-3.4.1.js b/docs/en/master/_static/jquery-3.4.1.js new file mode 100644 index 0000000..773ad95 --- /dev/null +++ b/docs/en/master/_static/jquery-3.4.1.js @@ -0,0 +1,10598 @@ +/*! + * jQuery JavaScript Library v3.4.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-05-01T21:04Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.4.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a global context + globalEval: function( code, options ) { + DOMEval( code, { nonce: options && options.nonce } ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.4 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2019-04-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) && + + // Support: IE 8 only + // Exclude object elements + (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && rdescend.test( selector ) ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = (elem.ownerDocument || elem).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( typeof elem.contentDocument !== "undefined" ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
    " ], + col: [ 2, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + } ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + // Support: IE 9-11 only + // Also use offsetWidth/offsetHeight for when box sizing is unreliable + // We use getClientRects() to check for hidden/disconnected. + // In those cases, the computed value can be trusted to be border-box + if ( ( !support.boxSizingReliable() && isBorderBox || + val === "auto" || + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = Date.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url, options ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/explanation/async.html b/docs/en/master/explanation/async.html new file mode 100644 index 0000000..b5b0496 --- /dev/null +++ b/docs/en/master/explanation/async.html @@ -0,0 +1,382 @@ + + + + + + + + Asynchronous Programming 101 - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Asynchronous Programming 101

    +
    +

    The Story

    +

    Let’s say we want to build a search engine. We’ll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this:

    +../_images/why_single_task.png +

    We have lots of web pages to index, so we simply handle them one by one:

    +../_images/why_throughput.png +

    Let’s assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores:

    +../_images/why_multicore.png +

    This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading:

    +../_images/why_multithreading.png +

    Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What’s wrong with multi-threading? From the diagram we can see:

    +
      +
    • There are yellow bars taking up extra time.

    • +
    • The green bars can still overlap with any bar in the other thread, but

    • +
    • non-green bars cannot overlap with non-green bars in the other thread.

    • +
    +

    The yellow bars are time taken by context switches, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let’s assume a world without +Hyper-threading or similar), +so in order to run several threads concurrently the CPU must split its +time into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point.

    +

    Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it’s waiting for the HTTP response (I/O). That’s how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won’t be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That’s also why this is called concurrency instead of parallelism.

    +

    As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous C10k +problem, usually solved by +asynchronous I/O:

    +../_images/why_coroutine.png +
    +

    Note

    +

    Asynchronous I/O and coroutines are two different things, but they usually +go together. Here we will stick with coroutines for simplicity.

    +
    +

    Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem.

    +
    +
    +

    Cooperative multitasking

    +

    So what is a coroutine?

    +

    In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread.

    +

    Threads are scheduled by the operating system using an approach called preemptive +multitasking. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn’t +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this:

    +
    Thread 1: I wanna run!
    +OS: Okay, here you go...
    +Thread 2: I wanna run!
    +OS: Urh, alright one sec ... Thread 1, hold on for a while!
    +Thread 1: Well I'm not done yet, but you are the boss.
    +OS: It won't be long. Thread 2 it's your turn now.
    +Thread 2: Yay! (&%#$@..+*&#)
    +Thread 1: Can I run now?
    +OS: Just a moment please ... Thread 2, give it a break!
    +Thread 2: Alright ... but I really need the CPU.
    +OS: You'll have it later. Thread 1, hurry up!
    +
    +
    +

    In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don’t - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +cooperative multitasking. It’s like this:

    +
    Coroutine 1: Let me know when event A arrives. I'm done here before that.
    +Event manager: Okay. What about you, coroutine 2?
    +Coroutine 2: Um I've got nothing to do here before event B.
    +Event manager: Cool, I'll be watching.
    +Event manager: (after a while) Hey coroutine 1, event A is here!
    +Coroutine 1: Awesome! Let me see ... looks good, but I need event C now.
    +Event manager: Very well. Seems event B arrived just now, coroutine 2?
    +Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done.
    +Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while.
    +(silence)
    +Event manager: Damn, event C timed out!
    +Coroutine 1: Arrrrh gotta kill myself with an exception :S
    +Event manager: Up to you :/
    +
    +
    +

    For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That’s why in the last diagram, +the red bars are not interleaved like threads.

    +
    +

    Tip

    +

    In Python and asyncio, async def declares coroutines, await yields +control to event loop (event manager).

    +
    +
    +
    +

    Pros and cons

    +

    Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput.

    +

    With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code.

    +

    However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple recv() operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to recv(), repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O.

    +

    Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, sleep(1) +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between await finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind.

    +

    Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It’s not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/explanation/engine.html b/docs/en/master/explanation/engine.html new file mode 100644 index 0000000..4ff7c2b --- /dev/null +++ b/docs/en/master/explanation/engine.html @@ -0,0 +1,689 @@ + + + + + + + + Engine and Connection - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Engine and Connection

    +

    GinoEngine is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together:

    +../_images/engine.png +

    Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users.

    +

    During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use.

    +
    +

    Note

    +

    In SQLAlchemy, database drivers are supposed to follow the DB-API standard, +which does not usually provide a pool implementation. Therefore, SQLAlchemy +has its own pool implementation, created directly in engine. This is where +this diagram doesn’t fit SQLAlchemy.

    +
    +

    The pool creates raw connections, not the GinoConnection +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we’ll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries.

    +

    On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use.

    +
    +

    Note

    +

    Another difference to SQLAlchemy here: GINO execution methods always return +final results, while in SQLAlchemy accessing the result may cause further +implicit database accesses. Therefore GINO engine immediately releases the +connection when the execution method on the engine returns, but SQLAlchemy +can only release the connection implicitly when the result data is found +exhausted.

    +

    By immediately releasing a connection, GINO may not release the related raw +connection when the raw connection was reused from another parent +connection. We’ll get to this later.

    +
    +

    GINO also supports implicit execution +without having to specify an engine or connection explicitly. This is done by +binding the engine to the db instance, also known as the +MetaData or the Gino instance. +You may possibly bind a GinoConnection instance, but that +is greatly not recommended because it is very much untested.

    +

    At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +db instance on creation, therefore the models can do implicit execution too +if their db has a bind.

    +

    Then let’s get to some details.

    +
    +

    Creating Engines

    +

    GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO’s strategy to create asynchronous GinoEngine is +just gino, but only available after gino is imported:

    +
    import gino, sqlalchemy
    +
    +async def main():
    +    e = await sqlalchemy.create_engine('postgresql://...', strategy='gino')
    +    # e is a GinoEngine
    +
    +
    +
    +

    Tip

    +

    Please read this SQLAlchemy document +to learn about writing database URLs.

    +
    +

    Also the GINO strategy replaces the default driver of dialect postgresql:// +from psycopg2 to asyncpg, so that you don’t have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +postgresql+asyncpg://... or just asyncpg://....

    +

    GINO also offers a shortcut as gino.create_engine(), which only sets the +default strategy to gino and does nothing more. So here is an identical +example:

    +
    import gino
    +
    +async def main():
    +    e = await gino.create_engine('postgresql://...')
    +    # e is also a GinoEngine
    +
    +
    +

    As you may have noticed, when using the GINO strategy, +create_engine() returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default.

    +

    For it is just SQLAlchemy create_engine(), the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!):

    +

    For Dialect:

    + +

    For Engine:

    + +

    While these parameters are discarded by GINO:

    + +

    In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from create_pool(). +For example, we can create an engine without initial connections:

    +
    e = await gino.create_engine('postgresql://...', min_size=0)
    +
    +
    +

    Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:

    +
    import sqlalchemy
    +
    +metadata = sqlalchemy.MetaData()
    +metadata.bind = 'postgresql://...'
    +
    +# or in short
    +
    +metadata = sqlalchemy.MetaData('postgresql://...')
    +
    +
    +

    This implicitly calls create_engine() under the hood. However +in GINO, creating an engine requires await, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass Gino, reverted it to simple assignment:

    +
    import gino
    +
    +db = gino.Gino()
    +
    +async def main():
    +    # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind
    +    engine = await gino.create_engine('postgresql://...')
    +    db.bind = engine
    +
    +
    +

    And provided a shortcut to do so:

    +
    engine = await db.set_bind('postgresql://...')
    +
    +
    +

    And another simpler shortcut for one-time usage:

    +
    db = await gino.Gino('postgresql://...')
    +
    +
    +

    To unset a bind and close the engine:

    +
    engine, db.bind = db.bind, None
    +await engine.close()
    +
    +
    +

    Or with a shortcut correspondingly:

    +
    await engine.pop_bind().close()
    +
    +
    +

    Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +
    +
    +

    Managing Connections

    +

    With a GinoEngine at hand, you can acquire connections +from the pool now:

    +
    conn = await engine.acquire()
    +
    +
    +

    Don’t forget to release it after use:

    +
    await conn.release()
    +
    +
    +

    Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    Here conn is a GinoConnection instance. As mentioned +previously, GinoConnection is mapped to an underlying raw +connection, as shown in following diagram:

    +../_images/connection.png +

    Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: reuse and +lazy. They are keyword arguments on acquire() +and by default switched off.

    +
    +

    reuse

    +

    When acquiring a GinoConnection (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +GinoConnection (2). This is the default behavior of +acquire() with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:

    +
    async with engine.acquire() as conn1:
    +    async with engine.acquire() as conn2:
    +        # conn2 is a completely different connection than conn1
    +
    +
    +

    But sometimes conn2 may exist in a different method:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner()
    +
    +async def inner():
    +    async with engine.acquire() as conn2:
    +        # ...
    +
    +
    +

    And we probably wish inner could reuse the same raw connection in +outer to save some resource, or borrow a new one if inner is +individually called without outer:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner(conn2=None):
    +    if conn2 is None:
    +        async with engine.acquire() as conn2:
    +            # ...
    +    else:
    +        # the same ... again
    +
    +
    +

    This is exactly the scenario reuse could be useful. We can simply tell the +acquire() to reuse the most recent reusable +connection in current context by setting reuse=True, as presented in this +identical example:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner():
    +    async with engine.acquire(reuse=True) as conn2:
    +        # ...
    +
    +
    +

    Back to previous diagram, the blue GinoConnection +instances (3, 4, 6) are “reusing connections” acquired with reuse=True, +while the green ones (2, 5, 7) are not, thus they become “reusable +connections”. The green reusable connections are put in a stack in current +context, so that acquire(reuse=True) always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack.

    +
    +

    Tip

    +

    By context, we are actually referring to the context concept in +contextvars the +new module in Python 3.7, and its partial backport aiocontextvars. Simply speaking, you may +treat a series of function calls in a chain as in the same context, even if +there is an await. It’s something like a thread local in asyncio.

    +
    +

    GinoConnection (2) may be created through +acquire(reuse=True) too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection.

    +

    Releasing a reusing connection won’t cause the reused raw connection being +returned to the pool, only directly releasing the reused +GinoConnection can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more.

    +
    +
    +

    lazy

    +

    As you may have found, GinoConnection (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set lazy=True on acquire.

    +

    A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, GinoConnection (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +
    +
    +

    On implementation level, lazy is extremely easy in +acquire(): if lazy=False then borrow a raw +connection, else do nothing. That’s it. Before executing a query or starting a +transaction, GinoConnection will always try to borrow a +raw connection if there is none present. This allows GINO to “transiently +release” a raw connection, while all GinoConnection +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don’t want to +waste a connection resource checked out for nothing. For example:

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +    await conn.release(permanent=False)        # release (8)
    +    await asyncio.sleep(10)                    # simulate long I/O work
    +    await conn.scalar('select now()')          # re-acquire a new raw connection,
    +                                               #   not necessarily the same (8)
    +
    +
    +

    When used together with reuse, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set lazy=False on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It’s been quite a while, let me post the same diagram again:

    +../_images/connection.png +
    +
    +

    reusable

    +

    Usually, you don’t have to worry about the two options reuse and lazy, +using the default acquire() will always create +a concrete GinoConnection with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use reusable=False on acquire. As shown in the diagram, the unreusable +GinoConnection is an orphan away from any stack:

    +
    async with engine.acquire():                    # (2)
    +    async with engine.acquire(reusable=False):  # the unreusable connection
    +        async with engine.acquire(reuse=True):  # (3)
    +
    +
    +

    Unreusable connections can be lazy. But it is usually meaningless to specify +both reuse=True and reusable=False at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn’t make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don’t use +acquire(reuse=True, reusable=False) unless you know what it does.

    +
    +
    +

    current_connection

    +

    Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +current_connection which is always the reusable +GinoConnection at the top of current stack, or None +if current stack is empty.

    +
    +

    Tip

    +

    The different between current_connection +and acquire(reuse=True) is, the +latter always produces a GinoConnection, while the +former may not.

    +
    +
    +
    +
    +

    Executing Queries

    +

    Once you have a GinoConnection instance, you can start +executing queries with it. There are 6 variants of the execute method: +all(), +first(), +one(), +one_or_none(), +scalar() and +status(). They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results:

    +
      +
    • all() returns all results in a +list, which may be empty when the query has no result, empty but +still a list.

    • +
    • first() returns the first result directly, +or None if there is no result at all. There is usually some optimization +behind the scene to efficiently get only the first result, instead of loading +the full result set into memory.

    • +
    • one() returns exactly one result. If there +is no result at all or if there are multiple results, an exception is raised.

    • +
    • one_or_none() is similar to +one(), but it returns None if there is +no result instead or raising an exception.

    • +
    • scalar() is similar to +first(), it returns the first value of the +first result. Quite convenient to just retrieve a scalar value from database, +like NOW(), MAX(), COUNT() or whatever generates a single value. +None is also returned when there is no result, it is up to you how to +distinguish no result and the first value is NULL.

    • +
    • status() executes the query and discard all +the query results at all. Instead it returns the execution status line as it +is, usually a textual string. Note, there may be no optimization to only +return the status without loading the results, so make your query generate +nothing if you don’t want any result.

    • +
    +

    By “result”, I meant RowProxy of SQLAlchemy - an +immutable row instance with both tuple and dict interfaces. +Database values are translated twice before they are eventually stored in a +RowProxy: first by the database driver (dialect) +from network payload to Python objects (see Type Conversion of +how asyncpg does this), second by SQLAlchemy +result_processor() depending on the actual +type and dialect.

    +

    The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy execute() (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +multiparams, all 4 methods will always return None discarding all +results. Likewise, the parameter values are processed twice too: first by +bind_processor() then the database driver.

    +

    GINO also supports SQLAlchemy +execution_options() provided either on +engine level, +connection level or on +queries. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling return_model and providing a model will make +all() and +first() return ORM model instance(s) instead +of RowProxy instance(s). See also +execution_options() for more information.

    +

    In addition, GINO has an iterate() method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a server-side cursor.

    +
    +
    +

    Implicit Execution

    +

    Acquire a GinoConnection and execute queries on it, that +is the most explicit way. You can also execute queries on a +GinoEngine instance. In this case, a connection will be +acquired with reuse=True for you implicitly, and released after returning:

    +
    await engine.scalar('select now()')
    +
    +
    +

    Equals to:

    +
    async with engine.acquire(reuse=True) as conn:
    +    await conn.scalar('select now()')
    +
    +
    +

    This allows you to easily write connectionless code. For example:

    +
    async def get_now():
    +    return await engine.scalar('select now()')
    +
    +async def main():
    +    async with engine.acquire():
    +        now = await get_now()
    +        await engine.status('UPDATE ...')
    +
    +
    +

    In this example, main() will take only one raw connection. get_now() +can also work alone out of any acquire() context, thanks to reuse.

    +

    Furthermore, GINO provides the same query APIs on Gino +directly. They are simply delegates to corresponding API methods on the +bind. This allows even engine-less programming:

    +
    db = gino.Gino()
    +
    +async def get_now():
    +    return await db.scalar('select now()')
    +
    +async def main():
    +    async with db.with_bind('postgresql://...'):
    +        now = await get_now()
    +        await db.status('UPDATE ...')
    +
    +
    +
    +

    Note

    +

    In this example we didn’t put the two queries in an acquire() block, so +they might be executed in two different connections.

    +
    +

    At last, the SQLAlchemy implicit execution +on queries also work in GINO, under an extension named gino:

    +
    await users_table.select().gino.all()
    +
    +
    +

    By default, the extension GinoExecutor is injected on +Executable as a property of name gino +at the creation of Gino instance. Therefore, any +Executable object has the gino +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the bind of the db instance.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/explanation/sa20.html b/docs/en/master/explanation/sa20.html new file mode 100644 index 0000000..b2ac53b --- /dev/null +++ b/docs/en/master/explanation/sa20.html @@ -0,0 +1,831 @@ + + + + + + + + SQLAlchemy 2.0 - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    SQLAlchemy 2.0

    +

    This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes.

    +

    SQLAlchemy 2.0 will +deliver many breaking API changes, and SQLAlchemy 1.4 will be the “interim” +version for people to eventually upgrade their software to use SQLAlchemy 2.0.

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    GINO

    SQLAlchemy

    Dialect

    Comments

    1.0.x

    1.3.x

    Custom

    Current (old-)stable.

    1.1.x

    1.3.x

    Custom

    Next old-stable.

    1.2.x

    1.3.x

    Custom

    Future old-stable (maybe).

    1.4.x

    1.4.x

    Upstream

    2.0 Interim.

    2.0.x

    2.0.x

    Upstream

    Future stable.

    2.1.x

    2.0.x

    Upstream

    Future stable iterations.

    +

    To make things easier, GINO will (luckily) also follow the same versions for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only.

    +

    At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won’t match SQLAlchemy versions.

    +
    +

    The Async Solution

    +

    Among all the exciting updates in SQLAlchemy 1.4 / 2.0, native async support is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet to mix asynchronous stuff into current code base, avoiding making everything +async.

    +

    Let’s say we have an asynchronous method to create an asyncpg connection:

    +
    import asyncpg
    +
    +async def connect():
    +    return await asyncpg.connect("postgresql:///")
    +
    +
    +

    And an end-user method to use it:

    +
    async def main():
    +    conn = await connect()
    +    now = await conn.fetchval("SELECT now()")
    +
    +
    +

    Now instead of directly calling connect() from main(), I would like to add some +additional logic - let’s say, a sanity check:

    +
    async def safe_connect():
    +    conn = await connect()
    +    try:
    +        await conn.execute("SELECT 1")
    +    except Exception:
    +        return None
    +    else:
    +        return conn
    +
    +
    +

    Then the end-user should modify main() to:

    +
     async def main():
    +     conn = await safe_connect()
    +     if conn:
    +         now = await conn.fetchval("SELECT now()")
    +
    +
    +

    OK, everything works so far, as they are all regular async code. Here’s the interesting +part: safe_connect() must not be an async def method. With SQLAlchemy 1.4+, we +could:

    +
     from sqlalchemy.util import await_only, greenlet_spawn
    +
    + def sync_safe_connect():
    +     conn = await_only(connect())
    +     try:
    +         await_only(conn.execute("SELECT 1"))
    +     except Exception:
    +         return None
    +     else:
    +         return conn
    +
    + async def safe_connect():
    +     return await greenlet_spawn(sync_safe_connect)
    +
    +
    +

    Behind the scene, greenlet_spawn() runs the given “sync” method in a greenlet, which +uses await_only() to switch to the event loop and bridge the underlying async +methods. As sync_safe_connect() is just a normal Python method, you can imagine how +it works together with lots of other “sync” code asynchronously.

    +

    We’re not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its “sync” code base, and still being able to provide async +APIs on top of them.

    +
    +
    +

    Async SQLAlchemy

    +

    Although greenlet might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move.

    +

    The sync library existed for years, with many assumptions like using threading.Lock +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. asyncio.Lock. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues.

    +

    As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, threading.Lock.acquire() actually works fine in a single coroutine, but 2 +concurrent coroutines +acquiring the same threading.Lock may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread.

    +

    Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base.

    +

    However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that’s GINO’s part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won’t work +because there is no place for await in accessing attributes (GINO doesn’t like such +implicitness anyways, so yeah).

    +

    To quickly get a picture of async SQLAlchemy (Core), here’s a sample from SQLAlchemy:

    +
    import asyncio
    +
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def async_main():
    +    engine = create_async_engine(
    +        "postgresql+asyncpg://scott:tiger@localhost/test", echo=True,
    +    )
    +
    +    async with engine.begin() as conn:
    +        await conn.run_sync(meta.drop_all)
    +        await conn.run_sync(meta.create_all)
    +
    +        await conn.execute(
    +            t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
    +        )
    +
    +    async with engine.connect() as conn:
    +
    +        # select a Result, which will be delivered with buffered
    +        # results
    +        result = await conn.execute(select(t1).where(t1.c.name == "some name 1"))
    +
    +        print(result.fetchall())
    +
    +
    +asyncio.run(async_main())
    +
    +
    +
    +
    +

    Auto-Commit Complication

    +

    After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that BEGIN starts a transaction and +COMMIT / ROLLBACK ends it. But what is happening to SQL statements that is not +wrapped in BEGIN ... COMMIT blocks?

    +
    +

    If you do not issue a BEGIN command, then each individual statement has an +implicit BEGIN and (if successful) COMMIT wrapped around it.

    +

    —PostgreSQL Documentation, 3.4. Transactions

    +
    +

    And yes, implicit ROLLBACK if not successful. This is not directly named as an +“auto-commit” feature, but PostgreSQL does enforce it. Now imagine a database whose +“auto-commit” feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed.

    +

    PEP 249 (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only commit() and rollback() on a +connection, but no begin(). So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call commit() to persist your changes. Closing a connection will +cause pending transactions rolled back automatically.

    +
    +

    Note that if the database supports an auto-commit feature, this (the auto-commit +feature – GINO comments) must be initially off.

    +

    —PEP 249

    +
    +

    As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +“auto-commit” has to mimic such behavior. For example, psycopg2 will automatically emit +a BEGIN to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen IDLE IN TRANSACTION?), sometimes even +holding database locks and eventually causing a deadlock storm.

    +

    To work around this workaround, PEP 249 does say:

    +
    +

    An interface method may be provided to turn it (the auto-commit feature) back on.

    +
    +

    So for psycopg2, one could do this:

    +
    import psycopg2
    +
    +conn = psycopg2.connect("postgresql:///")
    +conn.autocommit = True
    +conn.cursor().execute("SELECT now()")
    +
    +
    +

    Now the database correctly receives this SELECT statement only, without any implicit +BEGIN surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:

    +
    conn.cursor().execute("BEGIN")
    +conn.cursor().execute("UPDATE ...")
    +conn.cursor().execute("COMMIT")
    +
    +
    +

    Or 2) turn auto-commit off again:

    +
    conn.autocommit = False
    +conn.cursor().execute("UPDATE ...")
    +conn.commit()
    +
    +
    +

    I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg does provide a cleaner API, by not complying to PEP 249:

    +
    import asyncpg
    +
    +async def main():
    +    conn = await asyncpg.connect("postgresql://")
    +
    +    print(await conn.fetchval("SELECT now()"))  # SELECT now();
    +
    +    async with conn.transaction():              # BEGIN;
    +        await conn.execute("UPDATE ...")        # UPDATE ...;
    +                                                # COMMIT;
    +
    +
    +

    It’s much cleaner to see what’s actually happening on the wire to the database. This is +also how GINO works.

    +
    +
    +

    SQLAlchemy for DB-API

    +

    Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:

    +
    import sqlalchemy as sa
    +
    +e = sa.create_engine("postgresql:///", future=True)
    +with e.connect() as conn:
    +    conn.scalar(sa.text("SELECT now()"))
    +
    +
    +

    Only SELECT now()? No. Here’s the answer:

    +
    with e.connect() as conn:                 # BEGIN; SELECT version(); ...; ROLLBACK;
    +    conn.scalar(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                          # ROLLBACK;
    +
    +
    +
    +

    Note

    +

    We are using SQLAlchemy 2.0 API for simplification, by setting future=True using +SQLAlchemy 1.4. It’s way more complicated in SQLAlchemy 1.3 and I don’t want to get +into that.

    +
    +

    The reason behind this is, connections have “auto-commit” turned off by default. If +there is no transaction, an implicit BEGIN will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg and +simulated a compatible DB-API. Like this:

    +
    import sqlalchemy as sa
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def main():
    +    e = create_async_engine("postgresql+asyncpg:///")
    +    async with e.connect() as conn:                  # BEGIN; SELECT version(); ...; ROLLBACK;
    +        await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                     # ROLLBACK;
    +
    +
    +

    If you want to modify the database permanently, you have to commit() the implicit +transaction explicitly:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("UPDATE ..."))    # BEGIN; UPDATE ...;
    +         await conn.commit()                          # COMMIT;
    +
    +
    +

    Or use the explicit transaction API:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Please note that, SQLAlchemy 2.0 doesn’t allow soft-nested transactions. In other words, +you cannot nest 2 async with conn.begin(): blocks like this:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():      # BEGIN;
    +             async with conn.begin():  # Error: a transaction is already begun
    +                 ...
    +
    +
    +

    This limitation applies to implicit transactions too, even though it’s weird:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         await conn.rollback()                        # ROLLBACK;
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Similar to Core, SQLAlchemy ORM follows the same principal. Grab a session, use +it without begin(), and when you want to commit, commit(). Or, use an explicit +transaction in a with session.begin(): block. Personally I don’t like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more.

    +
    +
    +

    SQLAlchemy AUTOCOMMIT

    +

    I know you already miss the WYSIWYG asyncpg and GINO API. Hang in there, let’s build +GINO 1.4 together with the SQLAlchemy AUTOCOMMIT feature.

    +

    To turn AUTOCOMMIT back on, we need to set the isolation_level to AUTOCOMMIT in +execution_options:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Hooray! No more implicit BEGIN magic. We’re one step closer.

    +
    +

    Note

    +

    There is also a keyword argument:

    +
     e = create_async_engine(
    +     "postgresql+asyncpg:///",
    +     isolation_level="AUTOCOMMIT",
    + )
    +
    +
    +

    But this is implemented very differently than execution_options, and I don’t +think it’s working for GINO’s use case.

    +
    +

    The next question is, how do we explicitly start a transaction? Let’s try begin():

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    Wait a second … we’ve seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we’re in AUTOCOMMIT mode? Even +though the isolation_level tell the driver not to send BEGIN to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.begin():                     # no-op
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +
    +
    +

    Well, not quite what we expected. With AUTOCOMMIT set, all of begin(), commit() +and rollback() become no-ops.

    +

    Similar to the answers in psycopg2, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let’s try 2):

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    It’s working! According to SQLAlchemy docs, execution_options() creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well…

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                      # ROLLBACK;
    +
    +
    +

    Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same “DB-API” connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting isolation_level +modifies the value on “DB-API” connection.

    +

    Returning a SQLAlchemy connection back to the pool resets the isolation_level to its +default value, and acquiring the same connection again will initialize the +isolation_level with values from execution_options of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +isolation_level again:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         conn.execution_options(isolation_level="AUTOCOMMIT")
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Eventually we made it! 🎉

    +

    Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:

    +
    import gino
    +
    +async def main():
    +    engine = await gino.create_engine("postgresql:///")
    +    async with engine.acquire() as conn:
    +        await conn.scalar("SELECT now()")    # SELECT now();
    +
    +        async with conn.transaction():       # BEGIN;
    +            await conn.status("UPDATE ...")  # UPDATE ...;
    +                                             # COMMIT;
    +
    +
    +
    +

    Hint

    +

    Now I feel that “implementing” auto-commit feature is more like restoring to the +original database behavior, and having auto-commit turned off by default should be +considered as a new feature called “auto-begin” or “implicit transaction”. And it’s +a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem.

    +
    +
    +
    +

    Isolation Levels

    +

    By far, we only used 2 isolation_level values:

    +
      +
    • AUTOCOMMIT

    • +
    • READ COMMITTED

    • +
    +

    AUTOCOMMIT is not a valid PostgreSQL isolation level. It’s only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the “auto-begin” simulation.

    +

    READ COMMITTED is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction:

    +
    # BEGIN;
    +BEGIN
    +
    +# SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +# ROLLBACK;
    +ROLLBACK
    +
    +
    +

    To start a transaction in a different isolation level, you may:

    +
     # BEGIN ISOLATION LEVEL SERIALIZABLE;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit BEGIN in place, an implicit transaction is used. So this SQL also works +individually:

    +
    # SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +
    +

    But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels:

    +
     # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    + SET
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # BEGIN;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    + # BEGIN ISOLATION LEVEL READ COMMITTED;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  read committed
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    Then let’s see how SQLAlchemy with asyncpg solves this problem:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "SERIALIZABLE"},
    +     )
    +     async with e.connect() as conn:
    +         async with conn.begin():  # BEGIN ISOLATION LEVEL SERIALIZABLE;
    +             await conn.execute(sa.text("UPDATE ..."))  # UPDATE ...;
    +                                                        # COMMIT;
    +
    +
    +

    Under the neath, SQLAlchemy is leveraging asyncpg’s +Connection.transaction(isolation="...") to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions.

    +

    But there are 2 issues:

    +
      +
    • User-defined isolation level is not applied in PostgreSQL implicit transactions +(a.k.a. auto-commit statements), because no one SET SESSION.

    • +
    • asyncpg has a bug that Connection.transaction(isolation="read_committed") always +emit BEGIN without explicit isolation level, regardless of the actual default +isolation level.

    • +
    +

    The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL:

    +
     import sqlalchemy as sa
    + from sqlalchemy import event
    + from sqlalchemy.dialects.postgresql.base import PGDialect
    + from sqlalchemy.ext.asyncio import create_async_engine
    +
    +
    + async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +
    +     def set_isolation_level(dbapi_conn, record):
    +         PGDialect.set_isolation_level(
    +             e.sync_engine.dialect,
    +             dbapi_conn,
    +             "SERIALIZABLE",
    +         )
    +
    +     event.listen(e.sync_engine, "connect", set_isolation_level)
    +
    +     async with e.connect() as conn:
    +         print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL")))
    +         # Outputs: serializable
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/explanation/why.html b/docs/en/master/explanation/why.html new file mode 100644 index 0000000..ec62b73 --- /dev/null +++ b/docs/en/master/explanation/why.html @@ -0,0 +1,417 @@ + + + + + + + + Why Asynchronous ORM? - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Why Asynchronous ORM?

    +

    Conclusion first: in many cases, you don’t need to use an asynchronous ORM, or even +asyncio itself. But when you do, it’s very important to get it done correctly.

    +
    +

    When asyncio Helps

    +

    As Mike Bayer - the author of SQLAlchemy - pointed out, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn’t make sense to say “Hey I wanna use asyncio +because I love this asynchronous ORM”.

    +

    The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +Asynchronous Programming 101. So the ultimate reason to use asyncio should be the application itself, +not the database.

    +

    For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn’t send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it’ll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead.

    +../_images/263px-Minimum-Tonne.svg.png +

    Another example is authentication using OpenID Connect Authorization Code Flow as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it’s delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +Liebig’s law, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it’s not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave.

    +

    Arbitrary delays may happen in the database too. In PostgreSQL, you can LISTEN to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case.

    +

    In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is:

    +
    +
    +

    How to Access Database

    +

    The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let’s assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don’t get me wrong - asyncpg is great and convenient to use, but +there won’t be such a question “why asynchronous ORM” if we’re not seeking an objective +layer over bare SQL. It’s totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it’s just out of our scope here.

    +

    Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won’t work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here’s a very simple server:

    +
    import asyncio
    +from fastapi import FastAPI
    +from sqlalchemy import create_engine
    +from sqlalchemy.orm import sessionmaker
    +
    +app = FastAPI()
    +e = create_engine("postgresql://localhost")
    +Session = sessionmaker(bind=e)
    +
    +
    +@app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    This follows advices found in When do I construct a Session, when do I commit it, and when do I close it? from SQLAlchemy - linking +the session scope to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is kill -9.

    +

    What just happened is called a resource starvation. While the first 10 +requests were waiting for the async work (sleep(0.1)), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default max_overflow=10) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th’s acquisition.

    +

    The root cause of such starvation is making asynchronous calls in a transaction +scope created implicitly by the default autocommit=False. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in transaction scopes by explicitly ending them:

    +
    @app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        session.rollback()  # return the connection to avoid starvation
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    Because of the implicit nature of typical ORMs, it’s very hard to identify all such +transaction scopes and make sure there’s no await in them - you may have started +an implicit transaction by simply accessing a property like current_user.name. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this.

    +

    What about thread pool?

    +

    The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let’s assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool.

    +

    This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you’d want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you’re doing that in +the opposite way, you may want to reconsider the design.

    +

    In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs.

    +
    +
    +

    Asynchronous ORM Done Right

    +

    I would describe a proper asynchronous ORM as follows:

    +
    +

    Don’t Starve.

    +

    The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything async.

    +

    In the example above, if the connection acquisition is asynchronous, it won’t block the +main thread any more (it’ll block it’s own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:

    +
    @app.get("/")
    +async def read_root():
    +    async with db.acquire() as conn:  # }
    +        async with conn.begin():      # } These two won't block the main thread
    +            now = await db.scalar("SELECT now()")
    +            await asyncio.sleep(0.1)  # do some async work
    +            return str(now)
    +
    +
    +

    Even though this code won’t cause any resource starvation, using await within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won’t kill the server immediately, but +it has a few disadvantages:

    +
      +
    1. As the database connection pool is usually much smaller than the asynchronous server +concurrency, this kind of code caps the concurrency down to the DB pool level.

    2. +
    3. Taking a database connection from the pool for nothing is a waste of resource, +especially when the database pool is the shortest stave.

    4. +
    5. Database transactions should be kept short as much as possible. Because long-hanging +transactions may keep certain database locks, leading to performance issues or even +triggering a chain reaction of deadlocks.

    6. +
    +
    +
    +

    Be explicit.

    +

    With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an await or async with, so that you’ll know for sure when a +statement is trying to make any database I/O. Fortunately there’s no other way in +asyncio to do this - following the pattern of Twisted, asyncio is already forcing +explicit await for any asynchronous operations.

    +

    Being explicit in other part of the ORM design is also useful for enhancing the quality +of users’ code. This has nothing to do with asynchronous, it’s not a golden standard or +something with a clear cut either. For example:

    +
      +
    • We could design the ORM model instances to be stateless, so that the users don’t have +to learn and worry about maintaining the state of the instances.

    • +
    • There shouldn’t be any “buffered operations” which users could “flush” with a single +statement once for all.

    • +
    • The user should just give direct one-off commands and the ORM executes them right away.

    • +
    • Also I think the convenience tooling should be well-balanced, the user doesn’t have to +guess or remember what an API means - for example, there’re more than one ways to load +a many-to-one relationship, I’d prefer to write the query by myself rather than trying +to remember what “join_without_n_plus_1()” means.

    • +
    +
    +
    +

    Be productive.

    +

    Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two?

    +

    I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn’t harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO’s design.

    +

    Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they’ve learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again.

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/genindex.html b/docs/en/master/genindex.html new file mode 100644 index 0000000..270c3b2 --- /dev/null +++ b/docs/en/master/genindex.html @@ -0,0 +1 @@ +

    genindex.html

    \ No newline at end of file diff --git a/docs/en/master/how-to.html b/docs/en/master/how-to.html new file mode 100644 index 0000000..0db78d4 --- /dev/null +++ b/docs/en/master/how-to.html @@ -0,0 +1,293 @@ + + + + + + + + How-to Guides - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    How-to Guides

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/alembic.html b/docs/en/master/how-to/alembic.html new file mode 100644 index 0000000..b5a80a0 --- /dev/null +++ b/docs/en/master/how-to/alembic.html @@ -0,0 +1,304 @@ + + + + + + + + Use Alembic - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Use Alembic

    +

    Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO.

    +

    To add migrations to project first of all, add alembic as dependency:

    +
    $ pip install --user alembic
    +
    +
    +

    When you need to set up alembic for your project.

    +

    Prepare sample project. We will have a structure:

    +
    alembic_sample/
    +    my_app/
    +        models.py
    +
    +
    +

    Inside models.py define simple DB Model with GINO:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +
    +

    Set up Alembic

    +

    This will need to be done only once. Go to the main folder of your project +alembic_sample and run:

    +
    $ alembic init alembic
    +
    +
    +

    Alembic will create a bunch of files and folders in your project directory. One of them +will be alembic.ini. Open alembic.ini (you can find it in the main project +folder alembic_sample). Now change property sqlalchemy.url = with your DB +credentials. Like this:

    +
    sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    +
    +
    +

    Next go to folder alembic/ and open env.py file. Inside the env.py file you +need to import the db object. In our case db object is db from models +modules. This is a variable that links to your Gino() instance.

    +

    Inside alembic/env.py:

    +
    from main_app.models import db
    +
    +
    +

    And change target_metadata = to:

    +
    target_metadata = db
    +
    +
    +

    That’s it. We finished setting up Alembic for a project.

    +
    +

    Note

    +

    All alembic commands must be run always from the folder that contains the +alembic.ini file.

    +
    +
    +
    +

    Create first migration revision

    +

    Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema.

    +
    $ alembic revision -m "first migration" --autogenerate --head head
    +
    +
    +

    If you have any problems relative to package imports similar to this example:

    +
    File "alembic/env.py", line 7, in <module>
    +    from main_app.models import db
    +ModuleNotFoundError: No module named 'main_app'
    +
    +
    +

    Either install your project locally with pip install -e ., poetry install or +python setup.py develop, or add you package to PYTHONPATH, like this:

    +
    $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample
    +
    +
    +

    After the successful run of alembic revision in folder alembic/versions you will +see a file with new migration.

    +
    +
    +

    Apply migration on DB

    +

    Now time to apply migration to DB. It will create tables based on you DB Models.

    +
    $ alembic upgrade head
    +
    +
    +

    Great. Now you apply your first migration. Congratulations!

    +

    Next time, when you will make any changes in DB models just do:

    +
    $ alembic revision -m "your migration description" --autogenerate --head head
    +
    +
    +

    And

    +
    alembic upgrade head
    +
    +
    +

    Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/bakery.html b/docs/en/master/how-to/bakery.html new file mode 100644 index 0000000..435a713 --- /dev/null +++ b/docs/en/master/how-to/bakery.html @@ -0,0 +1,410 @@ + + + + + + + + Bake Queries - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Bake Queries

    +
    +

    New in version 1.1.

    +
    +

    Baked queries are used to boost execution performance for constantly-used queries. +Similar to the Baked Queries in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to bake them before creating the engine.

    +

    GINO provides two approaches for baked queries:

    +
      +
    1. Low-level Bakery API

    2. +
    3. High-level Gino.bake() integration

    4. +
    +
    +

    Use Bakery with Bare Engine

    +

    First, we need a bakery:

    +
    import gino
    +
    +bakery = gino.Bakery()
    +
    +
    +

    Then, let’s bake some queries:

    +
    db_time = bakery.bake("SELECT now()")
    +
    +
    +

    Or queries with parameters:

    +
    user_query = bakery.bake("SELECT * FROM users WHERE id = :uid")
    +
    +
    +

    Let’s assume we have this users table defined in SQLAlchemy Core:

    +
    import sqlalchemy as sa
    +
    +metadata = sa.MetaData()
    +user_table = sa.Table(
    +    "users", metadata,
    +    sa.Column("id", sa.Integer, primary_key=True),
    +    sa.Column("name", sa.String),
    +)
    +
    +
    +

    Now we can bake a similar query with SQLAlchemy Core:

    +
    user_query = bakery.bake(
    +    sa.select([user_table]).where(user.c.id == sa.bindparam("uid"))
    +)
    +
    +
    +

    These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:

    +
    engine = await gino.create_engine("postgresql://localhost/", bakery=bakery)
    +
    +
    +

    By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene.

    +

    To execute the baked queries, you could treat the BakedQuery +instances as if they are the queries themselves, for example:

    +
    now = await engine.scalar(db_time)
    +
    +
    +

    Pass in parameter values:

    +
    row = await engine.first(user_query, uid=123)
    +
    +
    +
    +
    +

    Use the Gino Integration

    +

    In a more common scenario, there will be a Gino instance, which has +usually a bind set - either explicitly or by the Web framework extensions:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        ...
    +
    +
    +

    A Bakery is automatically created in the db instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +db_time = db.bake("SELECT now()")
    +user_getter = db.bake(User.query.where(User.id == db.bindparam("uid")))
    +
    +
    +

    And the execution is also simplified with the same bind magic:

    +
    async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        print(await db_time.scalar())
    +
    +        user: User = await user_getter.first(uid=1)
    +        print(user.name)
    +
    +
    +

    To make things easier, you could even define the baked queries directly on the +model:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +    @db.bake
    +    def getter(cls):
    +        return cls.query.where(cls.id == db.bindparam("uid"))
    +
    +    @classmethod
    +    async def get(cls, uid):
    +        return await cls.getter.one_or_none(uid=uid)
    +
    +
    +

    Here GINO treats the getter() as a declared_attr() with +with_table=True, therefore it takes one positional argument cls for the User +class.

    +
    +
    +

    How to customize loaders?

    +

    If possible, you could bake the additional execution options into the query:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +)
    +
    +
    +

    The bake() method accepts keyword arguments as execution +options to e.g. simplify the example above into:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")),
    +    loader=User.load(comment="Added by loader."),
    +)
    +
    +
    +

    If the query construction is complex, bake() could also be +used as a decorator:

    +
    @db.bake
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +
    +
    +

    Or with short execution options:

    +
    @db.bake(loader=User.load(comment="Added by loader."))
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid"))
    +
    +
    +

    Meanwhile, it is also possible to override the loader at runtime:

    +
    user: User = await user_getter.load(User).first(uid=1)
    +print(user.name)  # no more comment on user!
    +
    +
    +
    +

    Hint

    +

    This override won’t affect the baked query - it’s used only in this execution.

    +
    +
    +
    +

    What APIs are available on BakedQuery?

    +

    BakedQuery is a GinoExecutor, so it inherited +all the APIs like all(), +first(), one(), +one_or_none(), scalar(), +status(), load(), +timeout(), etc.

    +

    GinoExecutor is actually the chained .gino helper API seen +usually in queries like this:

    +
    user = await User.query.where(User.id == 123).gino.first()
    +
    +
    +

    So a BakedQuery can be seen as a normal query with the .gino +suffix, plus it is directly executable.

    +
    +

    See also

    +

    Please see API document of gino.bakery for more information.

    +
    +
    +
    +

    I don’t want the prepared statements.

    +

    If you don’t need all the baked queries (m) to create prepared statements for all +the active database connections (n) in the beginning, you could set +prebake=False in the engine initialization to prevent the default initial +m x n prepare calls:

    +
    e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False)
    +
    +
    +

    Or if you’re using bind:

    +
    await db.set_bind("postgresql://...", prebake=False)
    +
    +
    +

    This is useful when you’re depending on db.gino.create_all() to create the tables, +because the prepared statements can only be created after the table creation.

    +

    The prepared statements will then be created and cached lazily on demand.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/contributing.html b/docs/en/master/how-to/contributing.html new file mode 100644 index 0000000..36fc284 --- /dev/null +++ b/docs/en/master/how-to/contributing.html @@ -0,0 +1,367 @@ + + + + + + + + Contributing - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Contributing

    +

    Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given.

    +

    You can contribute in many ways:

    +
    +

    Types of Contributions

    +
    +

    Report Bugs

    +

    Report bugs at https://github.com/python-gino/gino/issues.

    +

    If you are reporting a bug, please include:

    +
      +
    • Your operating system name and version.

    • +
    • Any details about your local setup that might be helpful in troubleshooting.

    • +
    • Detailed steps to reproduce the bug.

    • +
    +
    +
    +

    Fix Bugs

    +

    Look through the GitHub issues for bugs. Anything tagged with “bug” +and “help wanted” is open to whoever wants to implement it.

    +
    +
    +

    Implement Features

    +

    Look through the GitHub issues for features. Anything tagged with “enhancement” +and “help wanted” is open to whoever wants to implement it.

    +
    +
    +

    Write Documentation

    +

    GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such.

    +
    +
    +

    Submit Feedback

    +

    The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues.

    +

    If you are proposing a feature:

    +
      +
    • Explain in detail how it would work.

    • +
    • Keep the scope as narrow as possible, to make it easier to implement.

    • +
    • Remember that this is a volunteer-driven project, and that contributions +are welcome :)

    • +
    +
    +
    +
    +

    Get Started!

    +

    Ready to contribute? Here’s how to set up gino for local development.

    +
      +
    1. Fork the gino repo on GitHub.

    2. +
    3. Clone your fork locally:

      +
      $ git clone git@github.com:your_name_here/gino.git
      +
      +
      +
    4. +
    5. Create a branch for local development:

      +
      $ cd gino/
      +$ git checkout -b name-of-your-bugfix-or-feature
      +
      +
      +
    6. +
    +

    Now you can make your changes locally.

    +
      +
    1. Create virtual environment. Example for virtualenvwrapper:

      +
      $ mkvirtualenv gino
      +
      +
      +
    2. +
    3. Activate the environment and install requirements:

      +
      $ pip install -r requirements_dev.txt
      +
      +
      +
    4. +
    5. When you’re done making changes, check that your changes pass syntax checks:

      +
      $ flake8 gino tests
      +
      +
      +
    6. +
    +

    7. And tests (including other Python versions with tox). +For tests you you will need running database server (see “Tips” section below for configuration details):

    +
    $ pytest tests
    +$ tox
    +
    +
    +
      +
    1. For docs run:

      +
      $ make docs
      +
      +
      +
    2. +
    +

    It will build and open up docs in your browser.

    +
      +
    1. Commit your changes and push your branch to GitHub:

      +
      $ git add .
      +$ git commit -m "Your detailed description of your changes."
      +$ git push origin name-of-your-bugfix-or-feature
      +
      +
      +
    2. +
    3. Submit a pull request through the GitHub website.

    4. +
    +
    +
    +

    Pull Request Guidelines

    +

    Before you submit a pull request, check that it meets these guidelines:

    +
      +
    1. The pull request should include tests.

    2. +
    3. If the pull request adds functionality, the docs should be updated. Put +your new functionality into a function with a docstring, and add the +feature to the list in README.rst.

    4. +
    5. The pull request should work for Python 3.6, 3.7, 3.8 and 3.9. Check +https://github.com/python-gino/gino/actions?query=event%3Apull_request +and make sure that the tests pass for all supported Python versions.

    6. +
    +
    +
    +

    Tips

    +

    To run a subset of tests:

    +
    $ py.test -svx tests.test_gino
    +
    +
    +

    By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using ‘psql’ or similar:

    +
    CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino';
    +CREATE DATABASE gino WITH OWNER = gino;
    +
    +
    +

    Then run the tests like so:

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino
    +$ py.test
    +
    +
    +

    Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):

    +
    $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
    +$ openssl rsa -in privkey.pem -passin pass:abcd -out server.key
    +$ openssl req -x509 -in server.req -text -key server.key -out server.crt
    +$ chmod 600 server.key
    +$ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    +
    +
    +

    Terminal 2 (client):

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433
    +$ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'"
    +$ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;"
    +$ pytest tests/test_aiohttp.py
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/crud.html b/docs/en/master/how-to/crud.html new file mode 100644 index 0000000..816755c --- /dev/null +++ b/docs/en/master/how-to/crud.html @@ -0,0 +1,202 @@ + + + + + + + + CRUD - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    CRUD

    +

    THIS IS A WIP

    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/extensions.html b/docs/en/master/how-to/extensions.html new file mode 100644 index 0000000..36f2a62 --- /dev/null +++ b/docs/en/master/how-to/extensions.html @@ -0,0 +1,187 @@ + + + + + + + + Extensions - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    + + +
    +
    + + + + +
    +
    + +
    +
    + + +
    + EN +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/extensions/sanic.html b/docs/en/master/how-to/extensions/sanic.html new file mode 100644 index 0000000..0f9e533 --- /dev/null +++ b/docs/en/master/how-to/extensions/sanic.html @@ -0,0 +1,294 @@ + + + + + + + + Sanic Support - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + +
    + EN +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/extensions/starlette.html b/docs/en/master/how-to/extensions/starlette.html new file mode 100644 index 0000000..f707268 --- /dev/null +++ b/docs/en/master/how-to/extensions/starlette.html @@ -0,0 +1,291 @@ + + + + + + + + Starlette Support - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    Note

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + +
    + EN +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/extensions/tornado.html b/docs/en/master/how-to/extensions/tornado.html new file mode 100644 index 0000000..38a1925 --- /dev/null +++ b/docs/en/master/how-to/extensions/tornado.html @@ -0,0 +1,175 @@ + + + + + + + + Tornado Support - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    + + +
    + EN +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/faq.html b/docs/en/master/how-to/faq.html new file mode 100644 index 0000000..c4ad6d6 --- /dev/null +++ b/docs/en/master/how-to/faq.html @@ -0,0 +1,565 @@ + + + + + + + + Frequently Asked Questions - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Frequently Asked Questions

    +
    +

    SQLAlchemy 1.4 supports asyncio, what will GINO be?

    +

    Starting from 1.4, SQLAlchemy will support asyncio. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +greenlet. As an “async” user, you don’t +need to worry about its internals in most cases, it’s fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed.

    +

    Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features:

    +
      +
    • Contextual Connections (see Engine and Connection)

    • +
    • SQLAlchemy Core-based CRUD models

    • +
    • The GINO Loader system

    • +
    • Async MySQL support

    • +
    • Typing support NEW

    • +
    • Trio support NEW

    • +
    • Execution performance NEW

    • +
    +

    As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized.

    +
    +
    +

    ORM or not ORM?

    +

    GINO does perform the Object-Relational Mapping work under the +Data Mapper Pattern, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them “dirty” - or in a different way of thinking +they are always “dirty”. Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely non-ORM way.

    +
    +
    +

    Can I use features of SQLAlchemy ORM?

    +

    SQLAlchemy has two parts:

    +
      +
    • SQLAlchemy Core

    • +
    • SQLAlchemy ORM

    • +
    +

    GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won’t work in GINO.

    +
    +
    +

    How to join?

    +

    GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know how to write join in +SQLAlchemy. +Especially, Ádám made some amazing upgrades in +GINO #113 to make join easier, so +that you can use model classes directly as if they are tables in joining:

    +
    results = await User.join(Book).select().gino.all()
    +
    +
    +
    +
    +

    How to connect to database through SSL?

    +

    It depends on the dialect and database driver. For asyncpg, keyword arguments +on asyncpg.connect() are directly +available on create_engine() or db.set_bind(). Therefore, enabling SSL is rather easy:

    +
    engine = await gino.create_engine(..., ssl=True)
    +
    +
    +
    +
    +

    What is aiocontextvars and what does it do?

    +

    It is a partial backport of the new built-in module contextvars introduced in Python +3.7. In Python 3.6, aiocontextvars patches loop.create_task() +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2

    +

    If you are using Python 3.7, then aiocontextvars does nothing at all.

    +
    +

    Note

    +

    This answer is for GINO 0.8 and later, please check earlier versions of +this documentation if you are using GINO 0.7.

    +
    +
    +
    +

    How to define relationships?

    +

    GINO 1.0 or lower doesn’t provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see Loaders and Relationship for more information.

    +
    +
    +

    How to define index with multiple columns?

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    first_name = db.Column(db.Unicode())
    +    last_name = db.Column(db.Unicode())
    +
    +    _name_idx = db.Index('index_on_name', 'first_name', 'last_name')
    +
    +
    +

    The _name_idx is not used.

    +
    +
    +

    Is there a django admin interface for GINO?

    +

    Not quite yet, please follow this discussion.

    +
    +
    +

    How to use multiple databases for different users on the fly?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query.

    +

    In order to use multiple databases, you would need multiple +GinoEngine instances. Here’s a full example using FastAPI with +lazy engine creation:

    +
    from asyncio import Future
    +from contextvars import ContextVar
    +
    +from fastapi import FastAPI, Request
    +from gino import create_engine
    +from gino.ext.starlette import Gino
    +
    +engines = {}
    +dbname = ContextVar("dbname")
    +
    +
    +class ContextualGino(Gino):
    +    @property
    +    def bind(self):
    +        e = engines.get(dbname.get(""))
    +        if e and e.done():
    +            return e.result()
    +        else:
    +            return self._bind
    +
    +    @bind.setter
    +    def bind(self, val):
    +        self._bind = val
    +
    +
    +app = FastAPI()
    +db = ContextualGino(app)
    +
    +
    +@app.middleware("http")
    +async def lazy_engines(request: Request, call_next):
    +    name = request.query_params.get("db", "postgres")
    +    fut = engines.get(name)
    +    if fut is None:
    +        fut = engines[name] = Future()
    +        try:
    +            engine = await create_engine("postgresql://localhost/" + name)
    +        except Exception as e:
    +            fut.set_exception(e)
    +            del engines[name]
    +            raise
    +        else:
    +            fut.set_result(engine)
    +    await fut
    +    dbname.set(name)
    +    return await call_next(request)
    +
    +
    +@app.get("/")
    +async def get():
    +    return dict(dbname=await db.scalar("SELECT current_database()"))
    +
    +
    +
    +
    +

    How to load complex query results?

    +

    The API doc of gino.loader explains the available loaders, and there’re a few +examples in Loaders and Relationship too.

    +

    Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a TupleLoader with two sub-loaders - +ModelLoader and ColumnLoader:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Be ware of the tuple in .gino.load((...)).

    +
    +
    +

    How to do bulk or batch insert / update?

    +

    For a simple example, take a model that has one field, “name.” In your application you +have a list of names you would like to add to the database:

    +
    new_names = ["Austin", "Ali", "Jeff", "Marissa"]
    +
    +
    +

    To quickly insert the names in one query, first construct a dict with the +{"model_key": "value"} format:

    +
    new_names_dict = [dict(name=new_name) for new_name in new_names]
    +>> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}]
    +
    +
    +

    Finally, run an insert statement on the model:

    +
    await User.insert().gino.all(new_names_dict)
    +
    +
    +
    +
    +

    How to print the executed SQL?

    +

    GINO uses the same approach from SQLAlchemy: create_engine(..., echo=True). +(Or db.set_bind(..., echo=True)) Please see also here.

    +

    If you use any extension, you can also set that in config, by db_echo or DB_ECHO.

    +
    +
    +

    How to run EXISTS SQL?

    +
    await db.scalar(db.exists().where(User.email == email).select())
    +
    +
    +
    +
    +

    How to work with Alembic?

    +

    The fact that Gino is a MetaData is the +key to use Alembic. Just import and set target_metadata = db in Alembic env.py +will do. See Use Alembic for more details.

    +
    +
    +

    How to join the same table twice?

    +

    This is the same pattern as described in SQLAlchemy Adjacency List Relationships, where you +have a table with “a foreign key reference to itself”, or join the same table more than +once, “to represent hierarchical data in flat tables.” We’d need to use +alias(), for example:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.ForeignKey("users.id"))
    +
    +Parent = User.alias()
    +query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
    +users = await query.gino.load(User.load(parent=Parent)).all()
    +
    +
    +
    +
    +

    How to execute raw SQL with parameters?

    +

    Wrap the SQL with text(), and use keyword arguments:

    +
    query = db.text('SELECT * FROM users WHERE id = :id_val')
    +row = await db.first(query, id_val=1)
    +
    +
    +

    You may even load the rows into model instances:

    +
    query = query.execution_options(loader=User)
    +user = await db.first(query, id_val=1)
    +
    +
    +
    +
    +

    Gino engine is not initialized?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query. If bind is not set before +this, you’ll see this error:

    +
    gino.exceptions.UninitializedError: Gino engine is not initialized.
    +
    +
    +

    You could use either:

    +
      +
    • Call set_bind() or with_bind() to set the +bind on the Gino instance.

    • +
    • Use one of the Web framework extensions to set the bind for you in usually the server +start-up hook.

    • +
    • Use explicit bind for each execution, for example:

      +
      engine = await create_engine("...")
      +# ...
      +user = await User.get(request.user_id, bind=engine)
      +
      +
      +
    • +
    +
    +
    +

    How can I do SQL xxxx in GINO?

    +

    GINO uses SQLAlchemy Core queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy Table instances, and the column +attributes on GINO model classes are just SQLAlchemy Column +instances, you can use them in building your SQLAlchemy Core queries.

    +

    Alternatively, you could always execute the raw SQL directly, see How to execute raw SQL with parameters? above.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/json-props.html b/docs/en/master/how-to/json-props.html new file mode 100644 index 0000000..504c992 --- /dev/null +++ b/docs/en/master/how-to/json-props.html @@ -0,0 +1,383 @@ + + + + + + + + JSON Property - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    JSON Property

    +

    GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields.

    +
    +

    Quick Start

    +
    from gino import Gino
    +from sqlalchemy.dialects.postgresql import JSONB
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +    birthday = db.DateTimeProperty()
    +
    +
    +

    The age and birthday are JSON properties stored in the profile column. You +may use them the same way as a normal GINO model field:

    +
    u = await User.create(name="daisy", age=18)
    +print(u.name, u.age)  # daisy 18
    +
    +
    +
    +

    Note

    +

    profile is the default column name for all JSON properties in a model. If you +need a different column name for some JSON properties, you’ll need to specify +explicitly:

    +
    audit_profile = db.Column(JSON, nullable=False, server_default="{}")
    +
    +access_log = db.ArrayProperty(prop_name="audit_profile")
    +abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
    +
    +
    +
    +

    Using JSON properties in queries is supported:

    +
    await User.query.where(User.age > 16).gino.all()
    +
    +
    +

    This is simply translated into a native JSON query like this:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
    +
    +
    +

    Datetime type is very much the same:

    +
    from datetime import datetime
    +
    +await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
    +
    +
    +

    And the generated SQL:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
    +-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
    +
    +
    +

    Here’s a list of all the supported JSON properties:

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    JSON Property

    Python type

    JSON type

    Database Type

    StringProperty

    str

    string

    text

    IntegerProperty

    int

    number

    int

    BooleanProperty

    bool

    boolean

    boolean

    DateTimeProperty

    datetime

    string

    text

    ObjectProperty

    dict

    object

    JSON

    ArrayProperty

    list

    array

    JSON

    +
    +
    +

    Hooks

    +

    JSON property provides 2 instance-level hooks to customize the data:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @age.before_set
    +    def age(self, val):
    +        return val - 1
    +
    +    @age.after_get
    +    def age(self, val):
    +        return val + 1
    +
    +u = await User.create(name="daisy", age=18)
    +print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
    +
    +
    +

    And 1 class-level hook to customize the SQLAlchemy expression of the property:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    height = db.JSONProperty()
    +
    +    @height.expression
    +    def height(cls, exp):
    +        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
    +
    +
    +
    +
    +

    Create Index on JSON Properties

    +

    We’ll need to use declared_attr() to wait until the model class +is initialized. The rest is very much the same as defining a usual index:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @db.declared_attr
    +    def age_idx(cls):
    +        return db.Index("age_idx", cls.age)
    +
    +
    +

    This will lead to the SQL below executed if you run db.gino.create_all():

    +
    CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
    +
    +
    +
    +

    Warning

    +

    Alembic doesn’t support auto-generating revisions for functional indexes yet. You’ll +need to manually edit the revision. Please follow this issue for updates.

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/loaders.html b/docs/en/master/how-to/loaders.html new file mode 100644 index 0000000..4528207 --- /dev/null +++ b/docs/en/master/how-to/loaders.html @@ -0,0 +1,646 @@ + + + + + + + + Loaders and Relationship - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Loaders and Relationship

    +

    Loaders are used to load database row results into objects.

    +

    GINO doesn’t support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined.

    +
    +

    Model Loader

    +

    The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:

    +
    query = db.select([User])
    +rows = await query.gino.all()
    +
    +
    +

    In order to load rows into User objects, you can provide an execution +option loader with a new ModelLoader instance:

    +
    from gino.loader import ModelLoader
    +
    +query = db.select([User])
    +query = query.execution_options(loader=ModelLoader(User))
    +users = await query.gino.all()
    +
    +
    +

    The ModelLoader would then load each database row into a +User object. As this is frequently used, GINO made it a shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User.load())
    +users = await query.gino.all()
    +
    +
    +

    And another shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User)
    +users = await query.gino.all()
    +
    +
    +
    +

    Tip

    +

    User as loader is transformed into ModelLoader(User) by +Loader.get(), explained later in “Loader +Expression”.

    +
    +

    And again:

    +
    query = db.select([User])
    +users = await query.gino.load(User).all()
    +
    +
    +

    This is identical to the normal CRUD query:

    +
    users = await User.query.gino.all()
    +
    +
    +
    +
    +

    Loader Expression

    +

    So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than ModelLoader, +there’re also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders.

    +

    Here is an example using all loaders at once:

    +
    uid, user, sep, cols = await db.select([User]).gino.load(
    +    (
    +        User.id,
    +        User,
    +        '|',
    +        lambda row, ctx: len(row),
    +    )
    +).first()
    +
    +
    +

    Let’s check this piece by piece. Overall, the argument of +load() is a tuple. This is interpreted into a +TupleLoader, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a TupleLoader is a tuple.

    +

    Column in Loader Expressions are interpreted as +ColumnLoader. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, ColumnLoader uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use ColumnLoader, +you’ll have to declare columns for the query:

    +
    now = db.Column('time', db.DateTime())
    +result = await db.first(db.text(
    +    'SELECT now() AT TIME ZONE \'UTC\''
    +).columns(
    +    now,
    +).gino.load(
    +    ('now:', now)
    +).query)
    +print(*result)  # now: 2018-04-08 08:23:02.431847
    +
    +
    +

    Let’s get back to previous example. The second item in the tuple is a GINO +model class. As we’ve presented previously, it is interpreted into a +ModelLoader. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values.

    +
    +

    Tip

    +

    For a complex loader expression, the same row is given to all loaders, so +it doesn’t matter User.id is already used before the model loader.

    +
    +

    The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we’ll get to that later. Similar to +map(), the return value of the call will be the loaded result.

    +

    At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the '|' separator, it will show up as the third item +in every result returned by the query.

    +
    +
    +

    Many-to-One Relationship

    +

    A classic many-to-one relationship is also known as referencing - the model on +the “many” end keeps a single reference to the model on the “one” end. Although +GINO does not enforce it, usually people use a foreign key for the reference:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +

    So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:

    +
    async for child in Child.load(parent=Parent).gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    As you may have noticed, Child.load is exactly the shortcut to create +ModelLoader in the very first example. With some +additional keyword arguments, Child.load(parent=Parent) is still a +ModelLoader for Child, the model loader is at the +same time a query builder. It is identical to do this:

    +
    async for child in Child.load(parent=Parent).query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    The query dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The Loader simply forwarded unknown +attributes to its query, that’s why .query can +be omitted.

    +

    For ModelLoader, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using setattr(). For example, Parent is +interpreted as ModelLoader(Parent) which loads Parent instances, and +Parent instances are set as the parent attribute of the outer Child +instance.

    +
    +

    Warning

    +

    If multiple children references the same parent, then each child owns a +unique parent instance with identical values.

    +
    +
    +

    Tip

    +

    You don’t have to define parent attribute on Child. But if you do, +you gain the ability to customize how parent is stored or retrieved. For +example, let’s store the parent instance as _parent:

    +
    class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    _parent = None
    +
    +    @property
    +    def parent(self):
    +        return self._parent
    +
    +    @parent.setter
    +    def parent(self, value):
    +        self._parent = value
    +
    +
    +
    +

    The query builder works recursively. For ModelLoader, it +uses LEFT OUTER JOIN to connect the FROM clauses, in order to achieve +many-to-one scenario. The ON clause is determined automatically by foreign +keys. You can also customize the ON clause in case there is no foreign key +(a promise is a promise):

    +
    loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +async for child in loader.query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    And subloaders can be nested:

    +
    subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
    +
    +
    +

    By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values.

    +
    +
    +

    Self Referencing

    +
    +

    Warning

    +

    Experimental feature.

    +
    +

    Self referencing is usually used to create a tree-like structure. For example:

    +
    class Category(db.Model):
    +    __tablename__ = 'categories'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    +
    +
    +

    In order to load leaf categories with their parents, an alias is needed:

    +
    Parent = Category.alias()
    +
    +
    +

    Then the query would be something like this:

    +
    parents = db.select([Category.parent_id])
    +query = Category.load(parent=Parent.on(
    +    Category.parent_id == Parent.id
    +)).where(
    +    ~Category.id.in_(db.select([Category.alias().parent_id]))
    +)
    +async for c in query.gino.iterate():
    +    print(f'Leaf: {c.id}, Parent: {c.parent.id}')
    +
    +
    +

    The generated SQL looks like this:

    +
    SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id
    +  FROM categories LEFT OUTER JOIN categories AS categories_1
    +    ON categories.parent_id = categories_1.id
    + WHERE categories.id NOT IN (
    +           SELECT categories_2.parent_id
    +             FROM categories AS categories_2
    +       )
    +
    +
    +
    +
    +

    Other Relationships

    +

    GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    @children.setter
    +    def add_child(self, child):
    +        self._children.add(child)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child)).all()
    +
    +
    +

    Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the add_child setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +parent.add_child = new_child.

    +

    Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries.

    +

    GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you’ll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._child = None
    +
    +    @property
    +    def child(self):
    +        return self._child
    +
    +    @child.setter
    +    def child(self, child):
    +        self._child = child
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
    +
    +
    +

    Similarly, you can build many-to-many relationships in the same way:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    def add_child(self, child):
    +        self._children.add(child)
    +        child._parents.add(self)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._parents = set()
    +
    +    @property
    +    def parents(self):
    +        return self._parents
    +
    +
    +class ParentXChild(db.Model):
    +    __tablename__ = 'parents_x_children'
    +
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
    +
    +
    +query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
    +
    +
    +

    Likewise, there is for now no way to modify the relationships automatically, +you’ll have to manually create, delete or modify ParentXChild instances.

    +
    +
    +

    Advanced Usage of Loaders

    +

    You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For example, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Using alias to get ID-ascending pairs from the same table:

    +
    ua1 = User.alias()
    +ua2 = User.alias()
    +join_query = select([ua1, ua2]).where(ua1.id < ua2.id)
    +loader = ua1.load('id'), ua2.load('id')
    +result = await join_query.gino.load(loader).all()
    +print(result)  # e.g. [(1, 2), (1, 3), (2, 3)]
    +
    +
    +

    Potentially there could be a lot of different use cases of loaders. We’ll add +more inspiration here in the future.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/pool.html b/docs/en/master/how-to/pool.html new file mode 100644 index 0000000..4d7ef09 --- /dev/null +++ b/docs/en/master/how-to/pool.html @@ -0,0 +1,224 @@ + + + + + + + + Connection Pool - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Connection Pool

    +

    Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +NullPool), and users can define their own pools. +The base class should be Pool.

    +

    To use non-default pools in raw GINO:

    +
    from gino.dialects.asyncpg import NullPool
    +create_engine('postgresql://...', pool_class=NullPool)
    +
    +
    +

    To use non-default pools in extensions (taking Sanic as an example):

    +
    from gino.dialects.asyncpg import NullPool
    +from gino.ext.sanic import Gino
    +
    +app = sanic.Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_KWARGS = dict(
    +    pool_class=NullPool,
    +)
    +db = Gino()
    +db.init_app(app)
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/sanic.html b/docs/en/master/how-to/sanic.html new file mode 100644 index 0000000..200bc7e --- /dev/null +++ b/docs/en/master/how-to/sanic.html @@ -0,0 +1,280 @@ + + + + + + + + Sanic Support - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + + + +
    +
    + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/schema.html b/docs/en/master/how-to/schema.html new file mode 100644 index 0000000..db9921a --- /dev/null +++ b/docs/en/master/how-to/schema.html @@ -0,0 +1,426 @@ + + + + + + + + Schema Declaration - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Schema Declaration

    +

    There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy Table.

    +
    +

    GINO Engine

    +

    This is the minimized way to use GINO - using only +GinoEngine (and GinoConnection +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two.

    +

    For example, the table declaration is the same as SQLAlchemy core tutorial:

    +
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    +
    +metadata = MetaData()
    +
    +users = Table(
    +    'users', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('name', String),
    +    Column('fullname', String),
    +)
    +
    +addresses = Table(
    +    'addresses', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('user_id', None, ForeignKey('users.id')),
    +    Column('email_address', String, nullable=False)
    +)
    +
    +
    +
    +

    Note

    +

    When using GINO Engine only, it is usually your own business to create the +tables with either create_all() on a +normal non-async SQLAlchemy engine, or using Alembic. However it is still +possible to be done with GINO if it had to:

    +
    import gino
    +from gino.schema import GinoSchemaVisitor
    +
    +async def main():
    +    engine = await gino.create_engine('postgresql://...')
    +    await GinoSchemaVisitor(metadata).create_all(engine)
    +
    +
    +
    +

    Then, construct queries, in SQLAlchemy core too:

    +
    ins = users.insert().values(name='jack', fullname='Jack Jones')
    +
    +
    +

    So far, everything is still in SQLAlchemy. Now let’s get connected and execute +the insert:

    +
    async def main():
    +    engine = await gino.create_engine('postgresql://localhost/gino')
    +    conn = await engine.acquire()
    +    await conn.status(ins)
    +    print(await conn.all(users.select()))
    +    # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Here create_engine() creates a GinoEngine, +then acquire() checks out a +GinoConnection, and +status() executes the insert and returns the +status text. This works similarly as SQLAlchemy +execute() - they take the same parameters +but return a bit differently. There are also other similar query APIs:

    +
      +
    • all() returns a list of +RowProxy

    • +
    • first() returns one +RowProxy, or None

    • +
    • one() returns one +RowProxy

    • +
    • one_or_none() returns one +RowProxy, or None

    • +
    • scalar() returns a single value, or +None

    • +
    • iterate() returns an asynchronous iterator +which yields RowProxy

    • +
    +

    Please go to their API for more information.

    +
    +
    +

    GINO Core

    +

    In previous scenario, GinoEngine must not be set to +metadata.bind because it is not a +regular SQLAlchemy Engine thus it won’t work correctly. For this, GINO provides +a subclass of MetaData as Gino, +usually instantiated globally under the name of db. It can be used as a +normal MetaData still offering some conveniences:

    +
      +
    • It delegates most public types you can access on sqlalchemy

    • +
    • It works with both normal SQLAlchemy engine and asynchronous GINO engine

    • +
    • It exposes all query APIs on GinoConnection level

    • +
    • It injects two gino extensions on SQLAlchemy query clauses and schema +items, allowing short inline execution like users.select().gino.all()

    • +
    • It is also the entry for the third scenario, see later

    • +
    +

    Then we can achieve previous scenario with less code like this:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +users = db.Table(
    +    'users', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('name', db.String),
    +    db.Column('fullname', db.String),
    +)
    +
    +addresses = db.Table(
    +    'addresses', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('user_id', None, db.ForeignKey('users.id')),
    +    db.Column('email_address', db.String, nullable=False)
    +)
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await users.insert().values(
    +            name='jack',
    +            fullname='Jack Jones',
    +        ).gino.status()
    +        print(await users.select().gino.all())
    +        # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but sqlalchemy seems +never imported. This is useful when ORM is unwanted.

    +
    +

    Tip

    +

    asyncpgsa does the same thing, +but in a conceptually reversed way - instead of having asyncpg work for +SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that +way too because GINO is inspired by asyncpgsa). Either way works fine, it’s +just a matter of taste of whose API style to use, SQLAlchemy or asyncpg.

    +
    +
    +
    +

    GINO ORM

    +

    If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    fullname = db.Column(db.String)
    +
    +
    +class Address(db.Model):
    +    __tablename__ = 'addresses'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    user_id = db.Column(None, db.ForeignKey('users.id'))
    +    email_address = db.Column(db.String, nullable=False)
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await User.create(name='jack', fullname='Jack Jones')
    +        print(await User.query.gino.all())
    +        # Outputs: [<User object at 0x10a8ba860>]
    +
    +
    +
    +

    Important

    +

    The __tablename__ is a mandatory field to define a concrete model.

    +
    +

    As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in db. The class style is just +more declarative. Instead of users.c.name, you can now access the column by +User.name. The implicitly created Table is +available at User.__table__ and Address.__table__. You can use anything +that works in GINO core here.

    +
    +

    Note

    +

    Column names can be different as a class property and database column. +For example, name can be declared as +nickname = db.Column('name', db.Unicode(), default='noname'). In this +example, User.nickname is used to access the column, while in database, +the column name is name.

    +

    What’s worth mentioning is where raw SQL statements are used, or +TableClause is involved, like User.insert(), the original name is +required to be used, because in this case, GINO has no knowledge about the +mappings.

    +
    +
    +

    Tip

    +

    db.Model is a dynamically created parent class for your models. It is +associated with the db on initialization, therefore the table is put in +the very db when you declare your model class.

    +
    +

    Things become different when it comes to CRUD. You can use model level methods +to directly create() a model instance, instead of +inserting a new row. Or delete() a model instance +without needing to specify the where clause manually. Query returns model +instances instead of RowProxy, and row values are +directly available as attributes on model instances. See also: +CRUD.

    +

    After all, GinoEngine is always in use. Next let’s dig +more into it.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/starlette.html b/docs/en/master/how-to/starlette.html new file mode 100644 index 0000000..6e8e37e --- /dev/null +++ b/docs/en/master/how-to/starlette.html @@ -0,0 +1,277 @@ + + + + + + + + Starlette Support - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    Note

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + + + +
    +
    + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/tornado.html b/docs/en/master/how-to/tornado.html new file mode 100644 index 0000000..d3d6f01 --- /dev/null +++ b/docs/en/master/how-to/tornado.html @@ -0,0 +1,161 @@ + + + + + + + + Tornado Support - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    + + + + +
    +
    + + + + + \ No newline at end of file diff --git a/docs/en/master/how-to/transaction.html b/docs/en/master/how-to/transaction.html new file mode 100644 index 0000000..b7c7d9f --- /dev/null +++ b/docs/en/master/how-to/transaction.html @@ -0,0 +1,310 @@ + + + + + + + + Transaction - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Transaction

    +

    It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an await will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it.

    +
    +

    Basic usage

    +

    Transactions belong to GinoConnection. The most common +way to use transactions is through an async with statement:

    +
    async with connection.transaction() as tx:
    +    await connection.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    This guarantees a transaction is opened when entering the async with block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at raw_transaction, but in +most cases you don’t need to touch it.

    +

    GINO provides two convenient shortcuts to end the transaction early:

    + +

    They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the async with block after +raise_commit() or +raise_rollback() is skipped. The +internal exception is inherited from BaseException so that normal try +... except Exception block can’t trap it. This exception stops propagating at +the end of async with block, so you don’t need to worry about handling it.

    +

    Transactions can also be started on a GinoEngine:

    +
    async with engine.transaction() as tx:
    +    await engine.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    Here a GinoConnection is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The GinoConnection instance is accessible at +tx.connection. Other than +that, everything else is the same.

    +
    +

    Important

    +

    The implicit connection is by default borrowed with reuse=True. That +means using transaction() of +GinoEngine within a connection context is the same as +calling transaction() of the current +connection without having to reference it, no separate connection shall be +created.

    +
    +

    Similarly, if your Gino instance has a bind, you may also do +the same on it:

    +
    async with db.transaction() as tx:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +
    +
    +

    Nested Transactions

    +

    Transactions can be nested, nested transaction will create a savepoint as for +now on asyncpg. A similar example from asyncpg doc:

    +
    async with connection.transaction() as tx1:
    +    await connection.status('CREATE TABLE mytab (a int)')
    +
    +    # Create a nested transaction:
    +    async with connection.transaction() as tx2:
    +        await connection.status('INSERT INTO mytab (a) VALUES (1), (2)')
    +        # Rollback the nested transaction:
    +        tx2.raise_rollback()
    +
    +    # Because the nested transaction was rolled back, there
    +    # will be nothing in `mytab`.
    +    assert await connection.all('SELECT a FROM mytab') == []
    +
    +
    +

    As you can see, the raise_rollback() +breaks only the async with block of the specified tx2, the outer +transaction tx1 just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won’t trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate.

    +

    Because of the default reusing behavior, transactions on engine or db +follows the same nesting rules. Please see +GinoTransaction for more information.

    +
    +
    +

    Manual Control

    +

    Other than using async with, you can also manually control the +transaction:

    +
    tx = await connection.transaction()
    +try:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    You can’t use raise_commit() or +raise_rollback() here, similarly it is +prohibited to use commit() and +rollback() in an async with block.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/index.html b/docs/en/master/index.html new file mode 100644 index 0000000..1437722 --- /dev/null +++ b/docs/en/master/index.html @@ -0,0 +1,238 @@ + + + + + + + + Welcome to GINO’s documentation! - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Welcome to GINO’s documentation!

    +PyPI Release Version +GitHub Workflow Status for tests +Codacy coverage +Dependabot +

    GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy core for Python asyncio. Now (early 2020) GINO supports only one +dialect asyncpg.

    + + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/objects.inv b/docs/en/master/objects.inv new file mode 100644 index 0000000..944d005 Binary files /dev/null and b/docs/en/master/objects.inv differ diff --git a/docs/en/master/py-modindex.html b/docs/en/master/py-modindex.html new file mode 100644 index 0000000..18dcbf9 --- /dev/null +++ b/docs/en/master/py-modindex.html @@ -0,0 +1 @@ +

    domainindex.html

    \ No newline at end of file diff --git a/docs/en/master/reference.html b/docs/en/master/reference.html new file mode 100644 index 0000000..4e37a3e --- /dev/null +++ b/docs/en/master/reference.html @@ -0,0 +1,343 @@ + + + + + + + + Reference - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Reference

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api.html b/docs/en/master/reference/api.html new file mode 100644 index 0000000..3b17e2e --- /dev/null +++ b/docs/en/master/reference/api.html @@ -0,0 +1,249 @@ + + + + + + + + API Reference - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.aiocontextvars.html b/docs/en/master/reference/api/gino.aiocontextvars.html new file mode 100644 index 0000000..be39f05 --- /dev/null +++ b/docs/en/master/reference/api/gino.aiocontextvars.html @@ -0,0 +1,223 @@ + + + + + + + + gino.aiocontextvars module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.aiocontextvars module

    +
    +
    +gino.aiocontextvars.patch_asyncio()
    +

    Patches asyncio to support contextvars.

    +

    This is automatically called when gino is imported. If Python version is 3.7 +or greater, this function is a no-op.

    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.api.html b/docs/en/master/reference/api/gino.api.html new file mode 100644 index 0000000..b4ff4a3 --- /dev/null +++ b/docs/en/master/reference/api/gino.api.html @@ -0,0 +1,628 @@ + + + + + + + + gino.api module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.api module

    +
    +
    +class gino.api.Gino(bind=None, model_classes=None, query_ext=True, schema_ext=True, ext=True, **kwargs)
    +

    Bases: sqlalchemy.sql.schema.MetaData

    +

    All-in-one API class of GINO, providing several shortcuts.

    +

    This class is a subclass of SQLAlchemy +MetaData, therefore its instances can be used +as a normal MetaData object, e.g. used in +Alembic. In usual cases, you would +want to define one global Gino instance, usually under the name +of db, representing the database used in your application.

    +

    You may define tables in the official way +SQLAlchemy core recommended, but more often in GINO we define model classes +with db.Model as their parent class to represent tables, for its +objective interface and CRUD operations. Please read CRUD +for more information.

    +

    For convenience, Gino instance delegated all properties publicly +exposed by sqlalchemy, so that you can define tables / models +without importing sqlalchemy:

    +
    id = db.Column(db.BigInteger(), primary_key=True)
    +
    +
    +

    Similar to MetaData, a Gino object +can bind to a GinoEngine instance, hereby allowing +“implicit execution” through the gino +extension on Executable or +SchemaItem constructs:

    +
    await User.query.gino.first()
    +await db.gino.create_all()
    +
    +
    +

    Differently, GINO encourages the use of implicit execution and manages +transactional context correctly.

    +

    Binding to a connection object is not supported.

    +

    To set a bind property, you can simply set your +GinoEngine object on db.bind, or +set it to None to unbind. However, the creation of engine usually +happens at the same time. Therefore, GINO provided several convenient ways +doing so:

    +
      +
    1. with_bind() returning an asynchronous context manager:

      +
      async with db.with_bind('postgresql://...') as engine:
      +
      +
      +
    2. +
    3. set_bind() and pop_bind():

      +
      engine = await db.set_bind('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    4. +
    5. Directly await on Gino instance:

      +
      db = await gino.Gino('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    6. +
    +
    +

    Note

    +

    SQLAlchemy allows creating the engine by:

    +
    metadata.bind = 'postgresql://...'
    +
    +
    +

    While in GINO this only sets a string to bind, because +creating an engine requires await, which is exactly what +set_bind() does.

    +
    +

    At last, Gino delegates all query APIs on the bound +GinoEngine.

    +
    +
    +property Model
    +

    Declarative base class for models, subclass of +gino.declarative.Model. Defining subclasses of this class will +result new tables added to this Gino metadata.

    +
    + +
    +
    +acquire(*args, **kwargs)
    +

    A delegate of GinoEngine.acquire().

    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.all().

    +
    + +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    A delegate of Bakery.bake().

    +
    +

    New in version 1.1.

    +
    +
    + +
    +
    +property bakery
    +

    The bundled Bakery instance.

    +
    +

    New in version 1.1.

    +
    +
    + +
    +
    +property bind
    +

    An GinoEngine to which this Gino is bound.

    +

    This is a simple property with no getter or setter hook - what you set +is what you get. To achieve the same result as it is in SQLAlchemy - +setting a string or URL and getting an +engine instance, use set_bind() (or await on this +Gino object after setting a string or +URL).

    +
    + +
    +
    +compile(elem, *multiparams, **params)
    +

    A delegate of GinoEngine.compile().

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.first().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.iterate().

    +
    + +
    +
    +model_base_classes = (<class 'gino.crud.CRUDModel'>,)
    +

    Overridable default model classes to build the Model.

    +

    Default is (CRUDModel, ).

    +
    + +
    +
    +no_delegate = {'create_engine', 'engine_from_config'}
    +

    A set of symbols from sqlalchemy which is not delegated by +Gino.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one_or_none().

    +
    + +
    +
    +pop_bind()
    +

    Unbind self, and return the bound engine.

    +

    This is usually used in a chained call to close the engine:

    +
    await db.pop_bind().close()
    +
    +
    +
    +
    Returns
    +

    GinoEngine or None if self is not bound.

    +
    +
    +
    + +
    +
    +query_executor
    +

    The overridable gino extension class on +Executable.

    +

    This class will be set as the getter method of the property gino on +Executable and its subclasses, if +ext and query_ext arguments are both True. Default is +GinoExecutor.

    +

    alias of gino.api.GinoExecutor

    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.scalar().

    +
    + +
    +
    +schema_visitor
    +

    alias of gino.schema.GinoSchemaVisitor

    +
    + +
    +
    +async set_bind(bind, loop=None, **kwargs)
    +

    Bind self to the given GinoEngine and return it.

    +

    If the given bind is a string or +URL, all arguments will be sent to +create_engine() to create a new engine, and return it.

    +
    +
    Returns
    +

    GinoEngine

    +
    +
    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.status().

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    A delegate of GinoEngine.transaction().

    +
    + +
    +
    +with_bind(bind, loop=None, **kwargs)
    +

    Shortcut for set_bind() and pop_bind() plus closing engine.

    +

    This method accepts the same arguments of +create_engine(). This allows inline creating an engine and +binding self on enter, and unbinding self and closing the engine on +exit:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # play with engine
    +
    +
    +
    +
    Returns
    +

    An asynchronous context manager.

    +
    +
    +
    + +
    + +
    +
    +class gino.api.GinoExecutor(query)
    +

    Bases: object

    +

    The default gino extension on +Executable constructs for implicit +execution.

    +

    Instances of this class are created when visiting the gino property of +Executable instances (also referred as +queries or clause elements), for example:

    +
    await User.query.gino.first()
    +
    +
    +

    This allows GINO to add the asynchronous query APIs (all(), +first(), one(), one_or_none(), scalar(), +status(), iterate()) to SQLAlchemy query clauses without +messing up with existing synchronous ones. +Calling these asynchronous query APIs has the same restriction - the +relevant metadata (the Gino instance) must be bound to an engine, +or an AttributeError will be raised.

    +
    +

    Note

    +

    Executable clause elements that are completely irrelevant with any +table - for example db.select([db.text('now()')]) - has no +metadata, hence no engine. Therefore, this will always fail:

    +
    await db.select([db.text('now()')]).gino.scalar()
    +
    +
    +

    You should use conn.scalar(), +engine.scalar() or even +db.scalar() in this case.

    +
    +
    +
    +async all(*multiparams, **params)
    +

    Returns engine.all() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +execution_options(**options)
    +

    Set execution options to this query in a chaining call.

    +

    Read execution_options() for more +information.

    +
    +
    Parameters
    +

    options – Multiple execution options.

    +
    +
    +
    +

    New in version 1.1.

    +
    +
    + +
    +
    +async first(*multiparams, **params)
    +

    Returns engine.first() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +iterate(*multiparams, **params)
    +

    Returns engine.iterate() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +load(value)
    +

    Shortcut to set execution option loader in a chaining call.

    +

    For example to load Book instances with their authors:

    +
    query = Book.join(User).select()
    +books = await query.gino.load(Book.load(author=User)).all()
    +
    +
    +

    Read execution_options() for more +information.

    +
    + +
    +
    +model(model)
    +

    Shortcut to set execution option model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async one(*multiparams, **params)
    +

    Returns engine.one() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async one_or_none(*multiparams, **params)
    +

    Returns engine.one_or_none() +with this query as the first argument, and other arguments followed, +where engine is the GinoEngine to which the +metadata (Gino) is bound, while metadata is found in this +query.

    +
    + +
    +
    +property query
    +

    Get back the chained Executable.

    +

    In a chained query calls, occasionally the previous query clause is +needed after a .gino. chain, you can use .query. to resume the +chain back. For example:

    +
    await User.query.gino.model(FOUser).query.where(...).gino.all()
    +
    +
    +
    + +
    +
    +return_model(switch)
    +

    Shortcut to set execution option return_model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async scalar(*multiparams, **params)
    +

    Returns engine.scalar() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async status(*multiparams, **params)
    +

    Returns engine.status() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +timeout(timeout)
    +

    Shortcut to set execution option timeout in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.bakery.html b/docs/en/master/reference/api/gino.bakery.html new file mode 100644 index 0000000..76c2664 --- /dev/null +++ b/docs/en/master/reference/api/gino.bakery.html @@ -0,0 +1,330 @@ + + + + + + + + gino.bakery module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.bakery module

    +
    +
    +class gino.bakery.BakedQuery(elem, metadata, hash_=None)
    +

    Bases: gino.api.GinoExecutor

    +

    Represents a pre-compiled and possibly prepared query for faster execution.

    +

    BakedQuery is created by Bakery.bake(), and can be executed by +GinoEngine or GinoConnection. If there +is a proper bind in the baked query, contextual execution APIs inherited from +GinoExecutor can also be used.

    +
    +

    New in version 1.1.

    +
    +
    +
    +property bind
    +

    Internal API to provide a proper bind if found.

    +
    + +
    +
    +property compiled_sql
    +

    Internal API to get the SQLAlchemy compiled sql context.

    +
    + +
    +
    +execution_options(**kwargs)
    +

    Set execution options on a shadow query of this baked query.

    +

    The execution options set in this method won’t affect the execution options in +the baked query.

    +

    Read execution_options() for more +information.

    +
    +
    Parameters
    +

    options – Multiple execution options.

    +
    +
    Returns
    +

    A shadow of the baked query with new execution options but still +functions as a baked query.

    +
    +
    +
    + +
    +
    +get(_)
    +

    Internal API to get the compiled_sql.

    +
    +
    Parameters
    +

    _ – Ignored.

    +
    +
    +
    + +
    +
    +property query
    +

    Internal API to get the query instance before compilation.

    +
    + +
    +
    +property sql
    +

    Internal API to get the compiled raw SQL.

    +
    + +
    + +
    +
    +class gino.bakery.Bakery
    +

    Bases: object

    +

    Factory and warehouse of baked queries.

    +

    You may provide a bakery to a GinoEngine during creation as +the bakery keyword argument, and the engine will bake the queries and create +corresponding prepared statements for each of the connections in the pool.

    +

    A Gino instance has a built-in bakery, +it’s automatically given to the engine during set_bind() or +with_bind().

    +
    +

    New in version 1.1.

    +
    +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    Bake a query.

    +

    You can bake raw SQL strings or SQLAlchemy Core query instances. This method +adds the given query into a queue in the bakery, and bakes it only when the +bakery is set to an GinoEngine from which the bakery could +learn about the SQL dialect and compile the queries into SQL. Once done, the +bakery is “closed”, you can neither give it to another engine, nor use it to +bake more queries.

    +
    +
    Parameters
    +
      +
    • func_or_elem – A str or a SQLAlchemy Core query instance, or a +function that returns such results.

    • +
    • execution_options – Shortcut to add SQLAlchemy execution options to the +query.

    • +
    +
    +
    Returns
    +

    A BakedQuery instance.

    +
    +
    +
    + +
    +
    +query_cls
    +

    alias of gino.bakery.BakedQuery

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.crud.html b/docs/en/master/reference/api/gino.crud.html new file mode 100644 index 0000000..933685e --- /dev/null +++ b/docs/en/master/reference/api/gino.crud.html @@ -0,0 +1,660 @@ + + + + + + + + gino.crud module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.crud module

    +
    +
    +class gino.crud.Alias(model, *args, **kwargs)
    +

    Bases: object

    +

    Experimental proxy for table alias on model.

    +
    +
    +distinct(*columns)
    +
    + +
    +
    +load(*column_names, **relationships)
    +
    + +
    +
    +on(on_clause)
    +
    + +
    + +
    +
    +class gino.crud.CRUDModel(**values)
    +

    Bases: gino.declarative.Model

    +

    The base class for models with CRUD support.

    +

    Don’t inherit from this class directly, because it has no metadata. Use +db.Model instead.

    +
    +
    +classmethod alias(*args, **kwargs)
    +

    Experimental proxy for table alias on model.

    +
    + +
    +
    +append_where_primary_key(q)
    +

    Append where clause to locate this model instance by primary on the +given query, and return the new query.

    +

    This is mostly used internally in GINO, but also available for such +usage:

    +
    await user.append_where_primary_key(User.query).gino.first()
    +
    +
    +

    which is identical to:

    +
    await user.query.gino.first()
    +
    +
    +
    +

    Deprecated since version 0.7.6: Use lookup() instead.

    +
    +
    + +
    +
    +create(bind=None, timeout=<object object>, **values)
    +

    This create behaves a bit different on model classes compared to model +instances.

    +

    On model classes, create will create a new model instance and insert +it into database. On model instances, create will just insert the +instance into the database.

    +

    Under the hood create() uses INSERT ... RETURNING ... to +create the new model instance and load it with database default data if +not specified.

    +

    Some examples:

    +
    user1 = await User.create(name='fantix', age=32)
    +user2 = User(name='gino', age=42)
    +await user2.create()
    +
    +
    +
    +
    Parameters
    +
      +
    • bind – A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    • values – Keyword arguments are pairs of attribute names and their +initial values. Only available when called on a model class.

    • +
    +
    +
    Returns
    +

    The instance of this model class (newly created or existing).

    +
    +
    +
    + +
    +
    +delete
    +

    Similar to update(), this delete is also different on model +classes than on model instances.

    +

    On model classes delete is an attribute of type +Delete for massive deletes, for +example:

    +
    await User.delete.where(User.enabled.is_(False)).gino.status()
    +
    +
    +

    Similarly you can add a returning() +clause to the query and it shall return the deleted rows as model objects.

    +

    And on model instances, delete() is a method to remove the +corresponding row in the database of this model instance. and returns the +status returned from the database:

    +
    print(await user.delete())  # e.g. prints DELETE 1
    +
    +
    +
    +

    Note

    +

    delete() only removes the row from database, it does not affect the +current model instance.

    +
    +
    +
    Parameters
    +
      +
    • bind – An optional GinoEngine if current +metadata (Gino) has no bound engine, or specifying a +different GinoEngine to execute the DELETE.

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    +
    + +
    +
    +classmethod distinct(*columns)
    +

    Experimental loader feature to yield only distinct instances by given +columns.

    +
    + +
    +
    +async classmethod get(ident, bind=None, timeout=<object object>)
    +

    Get an instance of this model class by primary key.

    +

    For example:

    +
    user = await User.get(request.args.get('user_id'))
    +
    +
    +
    +
    Parameters
    +
      +
    • ident – Value of the primary key. For composite primary keys this +should be a tuple of values for all keys in database order, or a dict +of names (or position numbers in database order starting from zero) +of all primary keys to their values.

    • +
    • bind – A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    Returns
    +

    An instance of this model class, or None if no such row.

    +
    +
    +
    + +
    +
    +classmethod in_query(query)
    +

    Convenient method to get a Model object when using subqueries.

    +

    Though with filters and aggregations, subqueries often return same +columns as the original table, but SQLAlchemy could not recognize them +as the columns are in subqueries, so technically they’re columns in the +new “table”.

    +

    With this method, the columns are loaded into the original models when +being used in subqueries. For example:

    +
    query = query.alias('users')
    +MyUser = User.in_query(query)
    +
    +loader = MyUser.distinct(User1.id).load()
    +users = await query.gino.load(loader).all()
    +
    +
    +
    + +
    +
    +classmethod load(*column_names, **relationships)
    +

    Populates a loader.Loader instance to be used by the +loader execution option in order to customize the loading behavior +to load specified fields into instances of this model.

    +

    The basic usage of this method is to provide the loader execution +option (if you are looking for reloading +the instance from database, check get() or query) for a +given query.

    +

    This method takes both positional arguments and keyword arguments with +very different meanings. The positional arguments should be column +names as strings, specifying only these columns should be loaded into +the model instance (other values are discarded even if they are +retrieved from database). Meanwhile, the keyword arguments should be +loaders for instance attributes. For example:

    +
    u = await User.query.gino.load(User.load('id', 'name')).first()
    +
    +
    +
    +

    Tip

    +

    gino.load is a shortcut for setting the execution option +loader.

    +
    +

    This will populate a User instance with only id and name +values, all the rest are simply None even if the query actually +returned all the column values.

    +
    q = User.join(Team).select()
    +u = await q.gino.load(User.load(team=Team)).first()
    +
    +
    +

    This will load two instances of model User and Team, returning +the User instance with u.team set to the Team instance.

    +

    Both positional and keyword arguments can be used ath the same time. If +they are both omitted, like Team.load(), it is equivalent to just +Team as a loader.

    +

    Additionally, a loader.Loader instance can also be used to +generate queries, as its structure is usually the same as the query:

    +
    u = await User.load(team=Team).query.gino.first()
    +
    +
    +

    This generates a query like this:

    +
    SELECT users.xxx, ..., teams.xxx, ...
    +  FROM users LEFT JOIN teams
    +    ON ...
    +
    +
    +

    The Loader delegates attributes on the query, so +.query can be omitted. The LEFT JOIN is built-in behavior, +while the ON clause is generated based on foreign key. If there is +no foreign key, or the condition should be customized, you can use +this:

    +
    u = await User.load(
    +    team=Team.on(User.team_id == Team.id)).gino.first()
    +
    +
    +

    And you can use both load() and on() at the same time +in a chain, in whatever order suits you.

    +
    +

    See also

    +

    execution_options()

    +
    +
    + +
    +
    +lookup()
    +

    Generate where-clause expression to locate this model instance.

    +

    By default this method uses current values of all primary keys, and you +can override it to behave differently. Most instance-level CRUD +operations depend on this method internally. Particularly while +lookup() is called in update(), the where condition is +used in UpdateRequest.apply(), so that queries like UPDATE ... +SET id = NEW WHERE id = OLD could work correctly.

    +
    +
    Returns
    +

    +
    +
    +
    +

    New in version 0.7.6.

    +
    +
    + +
    +
    +classmethod none_as_none(enabled=True)
    +
    + +
    +
    +classmethod on(on_clause)
    +

    Customize the on-clause for the auto-generated outer join query.

    +
    +

    Note

    +

    This has no effect when provided as the loader execution option +for a given query.

    +
    +
    +

    See also

    +

    load()

    +
    +
    + +
    +
    +query
    +

    Get a SQLAlchemy query clause of the table behind this model. This equals +to sqlalchemy.select([self.__table__]). If this attribute is retrieved on a +model instance, then a where clause to locate this instance by its primary +key is appended to the returning query clause. This model type is set as +the execution option model in the returning clause, so by default the +query yields instances of this model instead of database rows.

    +
    + +
    +
    +select()
    +

    Build a query to retrieve only specified columns from this table.

    +

    This method accepts positional string arguments as names of attributes to +retrieve, and returns a Select for +query. The returning query object is always set with two execution options:

    +
      +
    1. model is set to this model type

    2. +
    3. return_model is set to False

    4. +
    +

    So that by default it always return rows instead of model instances, while +column types can be inferred correctly by the model option.

    +

    For example:

    +
    async for row in User.select('id', 'name').gino.iterate():
    +    print(row['id'], row['name'])
    +
    +
    +

    If select() is invoked on a model instance, then a WHERE clause +to locate this instance by its primary key is appended to the returning +query clause. This is useful when you want to retrieve a latest value of a +field on current model instance from database:

    +
    db_age = await user.select('age').gino.scalar()
    +
    +
    +
    +

    See also

    +

    execution_options()

    +
    +
    + +
    +
    +to_dict()
    +

    Convenient method to generate a dict from this model instance.

    +

    Keys will be attribute names, while values are loaded from memory (not +from database). If there are JSONProperty +attributes in this model, their source JSON field will not be included +in the returning dict - instead the JSON attributes will be.

    +
    +

    See also

    +

    json_support

    +
    +
    + +
    +
    +update
    +

    This update behaves quite different on model classes rather than model +instances.

    +

    On model classes, update is an attribute of type +Update for massive updates, for +example:

    +
    await User.update.values(enabled=True).where(...).gino.status()
    +
    +
    +

    Like query, the update query also has the model execution +option of this model, so if you use the +returning() clause, the query shall +return model objects.

    +

    However on model instances, update() is a method which accepts keyword +arguments only and returns an UpdateRequest to update this single +model instance. The keyword arguments are pairs of attribute names and new +values. This is the same as UpdateRequest.update(), feel free to +read more about it. A normal usage example would be like this:

    +
    await user.update(name='new name', age=32).apply()
    +
    +
    +

    Here, the await ... apply() executes the +actual UPDATE SQL in the database, while user.update() only makes +changes in the memory, and collect all changes into an +UpdateRequest instance.

    +
    + +
    + +
    +
    +class gino.crud.QueryModel
    +

    Bases: type

    +

    Metaclass of Model classes used for subqueries.

    +
    + +
    +
    +class gino.crud.UpdateRequest(instance: gino.crud.CRUDModel)
    +

    Bases: object

    +

    A collection of attributes and their new values to update on one model +instance.

    +

    UpdateRequest instances are created by CRUDModel.update, +don’t instantiate manually unless required. Every UpdateRequest +instance is bound to one model instance, all updates are for that one +specific model instance and its database row.

    +
    +
    +async apply(bind=None, timeout=<object object>)
    +

    Apply pending updates into database by executing an UPDATE SQL.

    +
    +
    Parameters
    +
      +
    • bind – A GinoEngine to execute the SQL, or +None (default) to use the bound engine in the metadata.

    • +
    • timeout – Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    Returns
    +

    self for chaining calls.

    +
    +
    +
    + +
    +
    +update(**values)
    +

    Set given attributes on the bound model instance, and add them into +the update collections for apply().

    +

    Given keyword-only arguments are pairs of attribute names and values to +update. This is not a coroutine, calling update() will have +instant effect on the bound model instance - its in-memory values will +be updated immediately. Therefore this can be used individually as a +shortcut to update several attributes in a batch:

    +
    user.update(age=32, disabled=True)
    +
    +
    +

    update() returns self for chaining calls to either +apply() or another update(). If one attribute is updated +several times by the same UpdateRequest, then only the last +value is remembered for apply().

    +

    Updated values can be SQLAlchemy expressions, for example an atomic +increment for user balance looks like this:

    +
    await user.update(balance=User.balance + 100).apply()
    +
    +
    +
    +

    Note

    +

    Expression values will not affect the in-memory attribute value on +update() before apply(), because it has no knowledge +of the latest value in the database. After apply() the new +value will be automatically reloaded from database with +RETURNING clause.

    +
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.declarative.html b/docs/en/master/reference/api/gino.declarative.html new file mode 100644 index 0000000..7c02ddc --- /dev/null +++ b/docs/en/master/reference/api/gino.declarative.html @@ -0,0 +1,408 @@ + + + + + + + + gino.declarative module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.declarative module

    +
    +
    +class gino.declarative.ColumnAttribute(prop_name, column)
    +

    Bases: object

    +

    The type of the column wrapper attributes on GINO models.

    +

    This is the core utility to enable GINO models so that:

    +
      +
    • Accessing a column attribute on a model class returns the column itself

    • +
    • Accessing a column attribute on a model instance returns the value for that column

    • +
    +

    This utility is customizable by defining __attr_factory__ in the model class.

    +
    + +
    +
    +class gino.declarative.InvertDict(*args, **kwargs)
    +

    Bases: dict

    +

    A custom dict that allows getting keys by values.

    +

    Used internally by Model.

    +
    +
    +invert_get(value, default=None)
    +

    Get key by value.

    +
    +
    Parameters
    +
      +
    • value – A value in this dict.

    • +
    • default – If specified value doesn’t exist, return default.

    • +
    +
    +
    Returns
    +

    The corresponding key if the value is found, or default otherwise.

    +
    +
    +
    + +
    + +
    +
    +class gino.declarative.Model
    +

    Bases: object

    +

    The base class of GINO models.

    +

    This is not supposed to be sub-classed directly, declarative_base() should +be used instead to generate a base model class with a given +MetaData. By defining subclasses of a model, instances +of sqlalchemy.schema.Table will be created and added to the bound +MetaData. The columns of the +Table instance are defined as +Column attributes:

    +
    from sqlalchemy import MetaData, Column, Integer, String
    +from gino.declarative import declarative_base
    +
    +Model = declarative_base()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = Column(Integer(), primary_key=True)
    +    name = Column(String())
    +
    +
    +

    The name of the columns are automatically set using the attribute name.

    +

    An instance of a model will maintain a memory storage for values of all the defined +column attributes. You can access these values by the same attribute name, or update +with new values, just like normal Python objects:

    +
    u = User()
    +assert u.name is None
    +
    +u.name = "daisy"
    +assert u.name == "daisy"
    +
    +
    +
    +

    Note

    +

    Accessing column attributes on a model instance will NOT trigger any database +operation.

    +
    +

    Constraint and Index are +also allowed as model class attributes. Their attribute names are not used.

    +

    A concrete model class can be used as a replacement of the +Table it reflects in SQLAlchemy queries. The model class +is also iterable, yielding all the Column instances +defined in the model.

    +

    Other built-in class attributes:

    +
      +
    • __metadata__

      +

      This is supposed to be set by declarative_base() and used only during +subclass construction. Still, this can be treated as a read-only attribute to +find out which MetaData this model is bound to.

      +
    • +
    • __tablename__

      +

      This is a required attribute to define a concrete model, meaning a +sqlalchemy.schema.Table instance will be created, added to the bound +MetaData and set to the class attribute __table__. +Not defining __tablename__ will result in an abstract model - no table +instance will be created, and instances of an abstract model are meaningless.

      +
    • +
    • __table__

      +

      This should usually be treated as an auto-generated read-only attribute storing +the sqlalchemy.schema.Table instance.

      +
    • +
    • __attr_factory__

      +

      An attribute factory that is used to wrap the actual +Column instance on the model class, so that the access +to the column attributes on model instances is redirected to the in-memory value +store. The default factory is ColumnAttribute, can be override.

      +
    • +
    • __values__

      +

      The internal in-memory value store as a dict, only available on model +instances. Accessing column attributes is equivalent to accessing __values__.

      +
    • +
    +
    + +
    +
    +gino.declarative.declarative_base(metadata, model_classes=(<class 'gino.declarative.Model'>, ), name='Model')
    +

    Create a base GINO model class for declarative table definition.

    +
    +
    Parameters
    +
      +
    • metadata – A MetaData instance to contain the +tables.

    • +
    • model_classes – Base class(es) of the base model class to be created. Default: +Model.

    • +
    • name – The class name of the base model class to be created. Default: +Model.

    • +
    +
    +
    Returns
    +

    A new base model class.

    +
    +
    +
    + +
    +
    +gino.declarative.declared_attr(m=None, *, with_table=False)
    +

    Mark a class-level method as a factory of attribute.

    +

    This is intended to be used as decorators on class-level methods of a +Model class. When initializing the class as well as its +subclasses, the decorated factory method will be called for each class, the +returned result will be set on the class in place of the factory method +under the same name.

    +

    @declared_attr is implemented differently than +declared_attr of SQLAlchemy, but they +are both more often used on mixins to dynamically declare indices or +constraints (also works for column and __table_args__, or even normal +class attributes):

    +
    class TrackedMixin:
    +    created = db.Column(db.DateTime(timezone=True))
    +
    +    @db.declared_attr
    +    def unique_id(cls):
    +        return db.Column(db.Integer())
    +
    +    @db.declared_attr
    +    def unique_constraint(cls):
    +        return db.UniqueConstraint('unique_id')
    +
    +    @db.declared_attr
    +    def poly(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.Column(db.Unicode())
    +
    +    @db.declared_attr
    +    def __table_args__(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.UniqueConstraint('poly'),
    +
    +
    +
    +

    Note

    +

    This doesn’t work if the model already had a __table__.

    +
    +
    +

    Changed in version 1.1: Added with_table parameter which works after the __table__ is created:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    ...
    +
    +    @db.declared_attr(with_table=True)
    +    def table_name(cls):
    +        # this is called only once when defining the class
    +        return cls.__table__.name
    +
    +assert User.table_name == "users"
    +
    +
    +
    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.dialects.aiomysql.html b/docs/en/master/reference/api/gino.dialects.aiomysql.html new file mode 100644 index 0000000..555a525 --- /dev/null +++ b/docs/en/master/reference/api/gino.dialects.aiomysql.html @@ -0,0 +1,595 @@ + + + + + + + + gino.dialects.aiomysql module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.aiomysql module

    +
    +
    +class gino.dialects.aiomysql.AiomysqlDBAPI
    +

    Bases: gino.dialects.base.BaseDBAPI

    +
    +
    +paramstyle = 'format'
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlDialect(*args, bakery=None, **kwargs)
    +

    Bases: sqlalchemy.dialects.mysql.base.MySQLDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.dialects.mysql.types._IntegerType'>: <class 'sqlalchemy.dialects.mysql.types._IntegerType'>, <class 'sqlalchemy.dialects.mysql.types._NumericType'>: <class 'sqlalchemy.dialects.mysql.types._NumericType'>, <class 'sqlalchemy.dialects.mysql.types._FloatType'>: <class 'sqlalchemy.dialects.mysql.types._FloatType'>, <class 'sqlalchemy.sql.sqltypes.Numeric'>: <class 'sqlalchemy.dialects.mysql.types.NUMERIC'>, <class 'sqlalchemy.sql.sqltypes.Float'>: <class 'sqlalchemy.dialects.mysql.types.FLOAT'>, <class 'sqlalchemy.sql.sqltypes.Time'>: <class 'sqlalchemy.dialects.mysql.types.TIME'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.MatchType'>: <class 'sqlalchemy.dialects.mysql.types._MatchType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.mysql.json.JSON'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONIndexType'>: <class 'sqlalchemy.dialects.mysql.json.JSONIndexType'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'sqlalchemy.dialects.mysql.json.JSONPathType'>, <class 'sqlalchemy.dialects.mysql.enumerated.ENUM'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.aiomysql.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of gino.dialects.aiomysql.DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of gino.dialects.aiomysql.AiomysqlDBAPI

    +
    + +
    +
    +driver = 'aiomysql'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of gino.dialects.aiomysql.AiomysqlExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given a DBAPI connection, return its isolation level.

    +

    When working with a _engine.Connection object, +the corresponding +DBAPI connection may be procured using the +_engine.Connection.connection accessor.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine isolation level facilities; +these APIs should be preferred for most typical use cases.

    +
    +

    See also

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +init_kwargs = {'auth_plugin', 'autocommit', 'bakery', 'charset', 'client_flag', 'connect_timeout', 'conv', 'cursorclass', 'db', 'host', 'init_command', 'local_infile', 'loop', 'maxsize', 'minsize', 'no_delay', 'password', 'pool_recycle', 'port', 'prebake', 'program_name', 'read_default_file', 'read_default_group', 'server_public_key', 'sql_mode', 'ssl', 'unix_socket', 'use_unicode', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument “conn” which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The “do_on_connect” callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    Returns
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    See also

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +postfetch_lastrowid = False
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given a DBAPI connection, set its isolation level.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine +isolation level facilities; these APIs should be preferred for +most typical use cases.

    +
    +

    See also

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +statement_compiler
    +

    alias of sqlalchemy.dialects.mysql.base.MySQLCompiler

    +
    + +
    +
    +support_prepare = False
    +
    + +
    +
    +support_returning = False
    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlExecutionContext
    +

    Bases: gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.mysql.base.MySQLExecutionContext

    +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +

    return self.cursor.lastrowid, or equivalent, after an INSERT.

    +

    This may involve calling special cursor functions, +issuing a new SELECT on the cursor (or a new one), +or returning a stored value that was +calculated within post_exec().

    +

    This function will only be called for dialects +which support “implicit” primary key generation, +keep preexecute_autoincrement_sequences set to False, +and when no explicit id value was bound to the +statement.

    +

    The function is called once, directly after +post_exec() and before the transaction is committed +or ResultProxy is generated. If the post_exec() +method assigns a value to self._lastrowid, the +value is used in place of calling get_lastrowid().

    +

    Note that this method is not equivalent to the +lastrowid method on ResultProxy, which is a +direct proxy to the DBAPI lastrowid accessor +in all cases.

    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlIterator(context, cursor)
    +

    Bases: gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AsyncEnum(*enums, **kw)
    +

    Bases: sqlalchemy.dialects.mysql.enumerated.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.DBAPICursor(dbapi_conn)
    +

    Bases: gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +iterate(context)
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.GinoNullType
    +

    Bases: sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +
      +
    • dialect – Dialect instance in use.

    • +
    • coltype – DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Pool(url, loop, init=None, bakery=None, prebake=True, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Transaction(conn, set_isolation=None)
    +

    Bases: gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.dialects.asyncpg.html b/docs/en/master/reference/api/gino.dialects.asyncpg.html new file mode 100644 index 0000000..8f190e0 --- /dev/null +++ b/docs/en/master/reference/api/gino.dialects.asyncpg.html @@ -0,0 +1,605 @@ + + + + + + + + gino.dialects.asyncpg module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.asyncpg module

    +
    +
    +class gino.dialects.asyncpg.AsyncEnum(*enums, **kw)
    +

    Bases: sqlalchemy.dialects.postgresql.base.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCompiler(dialect, statement, column_keys=None, inline=False, **kwargs)
    +

    Bases: sqlalchemy.dialects.postgresql.base.PGCompiler

    +
    +
    +property bindtemplate
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCursor(context, cursor)
    +

    Bases: gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDBAPI
    +

    Bases: gino.dialects.base.BaseDBAPI

    +
    +
    +Error = (<class 'asyncpg.exceptions._base.PostgresError'>, <class 'asyncpg.exceptions._base.InterfaceError'>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDialect(*args, bakery=None, **kwargs)
    +

    Bases: sqlalchemy.dialects.postgresql.base.PGDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.sql.sqltypes.ARRAY'>: <class 'sqlalchemy.dialects.postgresql.array.ARRAY'>, <class 'sqlalchemy.sql.sqltypes.Interval'>: <class 'sqlalchemy.dialects.postgresql.base.INTERVAL'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'gino.dialects.asyncpg.AsyncpgJSONPathType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.postgresql.json.JSON'>, <class 'sqlalchemy.dialects.postgresql.base.ENUM'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.asyncpg.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of gino.dialects.asyncpg.DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of gino.dialects.asyncpg.AsyncpgDBAPI

    +
    + +
    +
    +driver = 'asyncpg'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of gino.dialects.asyncpg.AsyncpgExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given an asyncpg connection, return its isolation level.

    +
    + +
    +
    +async has_schema(connection, schema)
    +
    + +
    +
    +async has_sequence(connection, sequence_name, schema=None)
    +

    Check the existence of a particular sequence in the database.

    +

    Given a _engine.Connection object and a string +sequence_name, return True if the given sequence exists in +the database, False otherwise.

    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +async has_type(connection, type_name, schema=None)
    +
    + +
    +
    +init_kwargs = {'bakery', 'command_timeout', 'connection_class', 'database', 'host', 'init', 'loop', 'max_cacheable_statement_size', 'max_cached_statement_lifetime', 'max_inactive_connection_lifetime', 'max_queries', 'max_size', 'min_size', 'passfile', 'password', 'port', 'prebake', 'record_class', 'server_settings', 'setup', 'ssl', 'statement_cache_size', 'timeout', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument “conn” which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The “do_on_connect” callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    Returns
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    See also

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given an asyncpg connection, set its isolation level.

    +
    + +
    +
    +statement_compiler
    +

    alias of gino.dialects.asyncpg.AsyncpgCompiler

    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgExecutionContext
    +

    Bases: gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.postgresql.base.PGExecutionContext

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgIterator(context, iterator)
    +

    Bases: object

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgJSONPathType
    +

    Bases: sqlalchemy.dialects.postgresql.json.JSONPathType

    +
    +
    +bind_processor(dialect)
    +

    Return a conversion function for processing bind values.

    +

    Returns a callable which will receive a bind parameter value +as the sole positional argument and will return a value to +send to the DB-API.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +

    dialect – Dialect instance in use.

    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.DBAPICursor(dbapi_conn)
    +

    Bases: gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.GinoNullType
    +

    Bases: sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    Parameters
    +
      +
    • dialect – Dialect instance in use.

    • +
    • coltype – DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.NullPool(url, loop, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.Pool(url, loop, bakery=None, prebake=True, **kwargs)
    +

    Bases: gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.PreparedStatement(prepared, clause=None)
    +

    Bases: gino.dialects.base.PreparedStatement

    +
    + +
    +
    +class gino.dialects.asyncpg.Transaction(tx)
    +

    Bases: gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.dialects.base.html b/docs/en/master/reference/api/gino.dialects.base.html new file mode 100644 index 0000000..2ab8581 --- /dev/null +++ b/docs/en/master/reference/api/gino.dialects.base.html @@ -0,0 +1,489 @@ + + + + + + + + gino.dialects.base module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.base module

    +
    +
    +class gino.dialects.base.AsyncDialectMixin
    +

    Bases: object

    +
    +
    +compile(elem, *multiparams, **params)
    +
    + +
    +
    +cursor_cls
    +

    alias of gino.dialects.base.DBAPICursor

    +
    + +
    +
    +classmethod dbapi()
    +
    + +
    +
    +dbapi_class
    +

    alias of gino.dialects.base.BaseDBAPI

    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +support_prepare = True
    +
    + +
    +
    +support_returning = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.base.BaseDBAPI
    +

    Bases: object

    +
    +
    +static Binary(x)
    +
    + +
    +
    +Error
    +

    alias of Exception

    +
    + +
    +
    +paramstyle = 'numeric'
    +
    + +
    + +
    +
    +class gino.dialects.base.Cursor
    +

    Bases: object

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.base.DBAPICursor
    +

    Bases: object

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +execute(statement, parameters)
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +executemany(statement, parameters)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.base.ExecutionContextOverride
    +

    Bases: object

    +
    +
    +baked_query = None
    +
    + +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +
    + +
    +
    +get_result_proxy()
    +
    + +
    +
    +loader
    +
    + +
    +
    +model
    +
    + +
    +
    +process_rows(rows, return_model=True)
    +
    + +
    +
    +return_model
    +
    + +
    +
    +timeout
    +
    + +
    + +
    +
    +class gino.dialects.base.Pool
    +

    Bases: object

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.base.PreparedStatement(clause=None)
    +

    Bases: object

    +
    +
    +async all(*multiparams, **params)
    +
    + +
    +
    +async first(*multiparams, **params)
    +
    + +
    +
    +iterate(*params, **kwargs)
    +
    + +
    +
    +async scalar(*multiparams, **params)
    +
    + +
    +
    +async status(*multiparams, **params)
    +
    + +
    + +
    +
    +class gino.dialects.base.Transaction
    +

    Bases: object

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.dialects.html b/docs/en/master/reference/api/gino.dialects.html new file mode 100644 index 0000000..ab94e5e --- /dev/null +++ b/docs/en/master/reference/api/gino.dialects.html @@ -0,0 +1,234 @@ + + + + + + + + gino.dialects package - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects package

    +
    +

    Submodules

    + +
    +
    +

    Module contents

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.engine.html b/docs/en/master/reference/api/gino.engine.html new file mode 100644 index 0000000..64989f1 --- /dev/null +++ b/docs/en/master/reference/api/gino.engine.html @@ -0,0 +1,725 @@ + + + + + + + + gino.engine module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.engine module

    +
    +
    +class gino.engine.GinoConnection(dialect, sa_conn, stack=None)
    +

    Bases: object

    +

    Represents an actual database connection.

    +

    This is the root of all query API like all(), first(), +one(), one_or_none(), scalar() or status(), +those on engine or query are simply wrappers of methods in this class.

    +

    Usually instances of this class are created by GinoEngine.acquire().

    +
    +

    Note

    +

    GinoConnection may refer to zero or one underlying database +connection - when a GinoConnection is acquired with +lazy=True, the underlying connection may still be in the pool, +until a query API is called or get_raw_connection() is called.

    +

    Oppositely, one underlying database connection can be shared by many +GinoConnection instances when they are acquired with +reuse=True. The actual database connection is only returned to the +pool when the root GinoConnection is released. Read more +in GinoEngine.acquire() method.

    +
    +
    +

    See also

    +

    Engine and Connection

    +
    +
    +
    +async all(clause, *multiparams, **params)
    +

    Runs the given query in database, returns all results as a list.

    +

    This method accepts the same parameters taken by SQLAlchemy +execute(). You can pass in a raw +SQL string, or any SQLAlchemy query clauses.

    +

    If the given query clause is built by CRUD models, then the returning +rows will be turned into relevant model objects (Only one type of model +per query is supported for now, no relationship support yet). See +execution_options() for more information.

    +

    If the given parameters are parsed as “executemany” - bulk inserting +multiple rows in one call for example, the returning result from +database will be discarded and this method will return None.

    +
    + +
    +
    +property dialect
    +

    The Dialect in use, inherited +from the engine created this connection.

    +
    + +
    +
    +execution_options(**opt)
    +

    Set non-SQL options for the connection which take effect during +execution.

    +

    This method returns a copy of this GinoConnection which +references the same underlying database connection, but with the given +execution options set on the copy. Therefore, it is a good practice to +discard the copy immediately after use, for example:

    +
    row = await conn.execution_options(model=None).first(User.query)
    +
    +
    +

    This is very much the same as SQLAlchemy +execution_options(), it +actually does pass the execution options to the underlying SQLAlchemy +Connection. Furthermore, GINO added a +few execution options:

    +
    +
    Parameters
    +
      +
    • return_model – Boolean to control whether the returning results +should be loaded into model instances, where the model class is +defined in another execution option model. Default is True.

    • +
    • model – Specifies the type of model instance to create on return. +This has no effect if return_model is set to False. Usually +in queries built by CRUD models, this execution option is +automatically set. For now, GINO only supports loading each row into +one type of model object, relationships are not supported. Please use +multiple queries for that. None for no postprocessing (default).

    • +
    • timeout – Seconds to wait for the query to finish. None for +no time out (default).

    • +
    • loader

      A loader expression to load the database rows into +specified objective structure. It can be either:

      +
        +
      • A model class, so that the query will yield model instances of this +class. It is your responsibility to make sure all the columns of +this model is selected in the query.

      • +
      • A Column instance, so that each result +will be only a single value of this column. Please note, if you +want to achieve fetching the very first value, you should use +first() instead of +scalar(). However, using directly +scalar() is a more direct way.

      • +
      • A tuple nesting more loader expressions recursively.

      • +
      • A callable() function that will be called for each row to +fully customize the result. Two positional arguments will be passed +to the function: the first is the row instance, the second is a context +object which is only present if nested else None.

      • +
      • A Loader instance directly.

      • +
      • Anything else will be treated as literal values thus returned as +whatever they are.

      • +
      +

    • +
    +
    +
    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +async get_raw_connection(*, timeout=None)
    +

    Get the underlying database connection, acquire one if none present.

    +
    +
    Parameters
    +

    timeout – Seconds to wait for the underlying acquiring

    +
    +
    Returns
    +

    Underlying database connection instance depending on the +dialect in use

    +
    +
    Raises
    +

    TimeoutError if the acquiring timed out

    +
    +
    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    Cursors must work within transactions:

    +
    async with conn.transaction():
    +    async for user in conn.iterate(User.query):
    +        # handle each user without loading all users into memory
    +
    +
    +

    Alternatively, you can manually control how the cursor works:

    +
    async with conn.transaction():
    +    cursor = await conn.iterate(User.query)
    +    user = await cursor.next()
    +    users = await cursor.many(10)
    +
    +
    +

    Read more about how Cursor works.

    +

    Similarly, this method takes the same parameters as all().

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs the given query in database, returns exactly one result.

    +

    If the query returns no result, this method will raise +NoResultFound. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs the given query in database, returns at most one result.

    +

    If the query returns no result, this method will return None. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async prepare(clause)
    +
    + +
    +
    +property raw_connection
    +

    The current underlying database connection instance, type depends on +the dialect in use. May be None if self is a lazy connection.

    +
    + +
    +
    +async release(*, permanent=True)
    +

    Returns the underlying database connection to its pool.

    +

    If permanent=False, this connection will be set in lazy mode with +underlying database connection returned, the next query on this +connection will cause a new database connection acquired. This is +useful when this connection may still be useful again later, while some +long-running I/O operations are about to take place, which should not +take up one database connection or even transaction for that long time.

    +

    Otherwise with permanent=True (default), this connection will be +marked as closed after returning to pool, and be no longer usable +again.

    +

    If this connection is a reusing connection, then only this connection +is closed (depending on permanent), the reused underlying +connection will not be returned back to the pool.

    +

    Practically it is recommended to return connections in the reversed +order as they are borrowed, but if this connection is a reused +connection with still other opening connections reusing it, then on +release the underlying connection will be returned to the pool, +with all the reusing connections losing an available underlying +connection. The availability of further operations on those reusing +connections depends on the given permanent value.

    +
    +

    See also

    +

    GinoEngine.acquire()

    +
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +schema_for_object = <sqlalchemy.sql.schema._SchemaTranslateMap object>
    +

    A SQLAlchemy compatibility attribute, don’t use it for now, it bites.

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the query status.

    +

    The returning query status depends on underlying database and the +dialect in use. For asyncpg it is a string, you can parse it like this: +https://git.io/v7oze

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    Starts a database transaction.

    +

    There are two ways using this method: managed as an asynchronous +context manager:

    +
    async with conn.transaction() as tx:
    +    # run query in transaction
    +
    +
    +

    or manually awaited:

    +
    tx = await conn.transaction()
    +try:
    +    # run query in transaction
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    Where the tx is an instance of the +GinoTransaction class, feel free to read +more about it.

    +

    In the first managed mode, the transaction is automatically committed +on exiting the context block, or rolled back if an exception was raised +which led to the exit of the context. In the second manual mode, you’ll +need to manually call the +commit() or +rollback() methods on need.

    +

    If this is a lazy connection, entering a transaction will cause a new +database connection acquired if none was present.

    +

    Transactions may support nesting depending on the dialect in use. For +example in asyncpg, starting a second transaction on the same +connection will create a save point in the database.

    +

    For now, the parameters are directly passed to underlying database +driver, read asyncpg.connection.Connection.transaction() for +asyncpg.

    +
    + +
    + +
    +
    +class gino.engine.GinoEngine(dialect, pool, loop, logging_name=None, echo=None, execution_options=None)
    +

    Bases: object

    +

    Connects a Pool and +Dialect together to provide a source +of database connectivity and behavior.

    +

    A GinoEngine object is instantiated publicly using the +gino.create_engine() function or +db.set_bind() method.

    +
    +

    See also

    +

    Engine and Connection

    +
    +
    +
    +acquire(*, timeout=None, reuse=False, lazy=False, reusable=True)
    +

    Acquire a connection from the pool.

    +

    There are two ways using this method - as an asynchronous context +manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    which will guarantee the connection is returned to the pool when +leaving the async with block; or as a coroutine:

    +
    conn = await engine.acquire()
    +try:
    +    # play with the connection
    +finally:
    +    await conn.release()
    +
    +
    +

    where the connection should be manually returned to the pool with +conn.release().

    +

    Within the same context (usually the same Task, see +also Transaction), a nesting acquire by default re

    +
    +
    Parameters
    +
      +
    • timeout – Block up to timeout seconds until there is one free +connection in the pool. Default is None - block forever until +succeeded. This has no effect when lazy=True, and depends on the +actual situation when reuse=True.

    • +
    • reuse – Reuse the latest reusable acquired connection (before +it’s returned to the pool) in current context if there is one, or +borrow a new one if none present. Default is False for always +borrow a new one. This is useful when you are in a nested method call +series, wishing to use the same connection without passing it around +as parameters. See also: Transaction. A reusing +connection is not reusable even if reusable=True. If the reused +connection happened to be a lazy one, then the reusing connection is +lazy too.

    • +
    • lazy – Don’t acquire the actual underlying connection yet - do it +only when needed. Default is False for always do it immediately. +This is useful before entering a code block which may or may not make +use of a given connection object. Feeding in a lazy connection will +save the borrow-return job if the connection is never used. If +setting reuse=True at the same time, then the reused connection - +if any - applies the same laziness. For example, reusing a lazy +connection with lazy=False will cause the reused connection to +acquire an underlying connection immediately.

    • +
    • reusable – Mark this connection as reusable or otherwise. This +has no effect if it is a reusing connection. All reusable connections +are placed in a stack, any reusing acquire operation will always +reuse the top (latest) reusable connection. One reusable connection +may be reused by several reusing connections - they all share one +same underlying connection. Acquiring a connection with +reusable=False and reusing=False makes it a cleanly isolated +connection which is only referenced once here.

    • +
    +
    +
    Returns
    +

    A GinoConnection object.

    +
    +
    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    Acquires a connection with reuse=True and runs +all() on it. reuse=True means you can safely +do this without borrowing more than one underlying connection:

    +
    async with engine.acquire():
    +    await engine.all('SELECT ...')
    +
    +
    +

    The same applies for other query methods.

    +
    + +
    +
    +async close()
    +

    Close the engine, by closing the underlying pool.

    +
    + +
    +
    +compile(clause, *multiparams, **params)
    +

    A shortcut for compile() on +the dialect, returns raw SQL string and parameters according to the +rules of the dialect.

    +
    + +
    +
    +connection_cls
    +

    Customizes the connection class to use, default is +GinoConnection.

    +

    alias of gino.engine.GinoConnection

    +
    + +
    +
    +property current_connection
    +

    Gets the most recently acquired reusable connection in the context. +None if there is no such connection.

    +
    +
    Returns
    +

    GinoConnection

    +
    +
    +
    + +
    +
    +property dialect
    +

    Read-only property for the +Dialect of this engine.

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs first(), See all().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    This requires that there is a reusable connection in the current +context, and an active transaction is present. Then its +GinoConnection.iterate() is executed and returned.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs one(), See all().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs one_or_none(), See all().

    +
    + +
    +
    +property raw_pool
    +

    Read-only access to the underlying database connection pool instance. +This depends on the actual dialect in use, Pool +of asyncpg for example.

    +
    + +
    +
    +repr(color=False)
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs scalar(), See all().

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs status(). See also all().

    +
    + +
    +
    +transaction(*args, timeout=None, reuse=True, reusable=True, **kwargs)
    +

    Borrows a new connection and starts a transaction with it.

    +

    Different to GinoConnection.transaction(), transaction on engine +level supports only managed usage:

    +
    async with engine.transaction() as tx:
    +    # play with transaction here
    +
    +
    +

    Where the implicitly acquired connection is available as +tx.connection.

    +

    By default, transaction() acquires connection with +reuse=True and reusable=True, that means it by default tries to +create a nested transaction instead of a new transaction on a new +connection. You can change the default behavior by setting these two +arguments.

    +

    The other arguments are the same as +transaction() on connection.

    + +
    +
    Returns
    +

    A asynchronous context manager that yields a +GinoTransaction

    +
    +
    +
    + +
    +
    +update_execution_options(**opt)
    +

    Update the default execution_options dictionary +of this GinoEngine.

    + +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.exceptions.html b/docs/en/master/reference/api/gino.exceptions.html new file mode 100644 index 0000000..a6d17ac --- /dev/null +++ b/docs/en/master/reference/api/gino.exceptions.html @@ -0,0 +1,257 @@ + + + + + + + + gino.exceptions module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.exceptions module

    +
    +
    +exception gino.exceptions.GinoException
    +

    Bases: Exception

    +
    + +
    +
    +exception gino.exceptions.InitializedError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.MultipleResultsFound
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoResultFound
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoSuchRowError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UninitializedError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UnknownJSONPropertyError
    +

    Bases: gino.exceptions.GinoException

    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.ext.html b/docs/en/master/reference/api/gino.ext.html new file mode 100644 index 0000000..9163563 --- /dev/null +++ b/docs/en/master/reference/api/gino.ext.html @@ -0,0 +1,234 @@ + + + + + + + + gino.ext package - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.ext package

    +
    +

    Module contents

    +

    Namespace package for GINO extensions.

    +

    This namespace package didn’t use any of the 3 official solutions. Instead, +gino.ext adds a hook into sys.meta_path, and utilize the Entry points +to find the extensions.

    +

    Any GINO extension package should provide an entry point like this:

    +
    [gino.extensions]
    +starlette = gino_starlette
    +
    +
    +

    So that the Python package gino_starlette will also be importable through +gino.ext.starlette.

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.html b/docs/en/master/reference/api/gino.html new file mode 100644 index 0000000..3df57d3 --- /dev/null +++ b/docs/en/master/reference/api/gino.html @@ -0,0 +1,287 @@ + + + + + + + + gino package - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino package

    +
    +

    Subpackages

    + +
    +
    +

    Submodules

    + +
    +
    +

    Module contents

    +
    +
    +gino.create_engine(*args, **kwargs)
    +

    Shortcut for sqlalchemy.create_engine() with strategy="gino".

    +
    +

    Changed in version 1.1: Added the bakery keyword argument, please see Bakery.

    +
    +
    +

    Changed in version 1.1: Added the prebake keyword argument to choose when to create the prepared +statements for the queries in the bakery:

    +
      +
    • Pre-bake immediately when connected to the database (default).

    • +
    • No pre-bake but create prepared statements lazily when needed for the first +time.

    • +
    +

    Note: prebake has no effect in aiomysql

    +
    +
    + +
    +
    +gino.get_version()
    +

    Get current GINO version.

    +
    + +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.json_support.html b/docs/en/master/reference/api/gino.json_support.html new file mode 100644 index 0000000..b1563de --- /dev/null +++ b/docs/en/master/reference/api/gino.json_support.html @@ -0,0 +1,357 @@ + + + + + + + + gino.json_support module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.json_support module

    +
    +
    +class gino.json_support.ArrayProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.BooleanProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.DateTimeProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.IntegerProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.JSONProperty(default=None, prop_name='profile')
    +

    Bases: object

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +get_profile(instance)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    +
    +reload(instance)
    +
    + +
    +
    +save(instance, value=<object object>)
    +
    + +
    + +
    +
    +class gino.json_support.ObjectProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.StringProperty(default=None, prop_name='profile')
    +

    Bases: gino.json_support.JSONProperty

    +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.loader.html b/docs/en/master/reference/api/gino.loader.html new file mode 100644 index 0000000..555893d --- /dev/null +++ b/docs/en/master/reference/api/gino.loader.html @@ -0,0 +1,645 @@ + + + + + + + + gino.loader module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.loader module

    +
    +
    +class gino.loader.AliasLoader(alias, *columns, **extras)
    +

    Bases: gino.loader.ModelLoader

    +

    The same as ModelLoader, kept for compatibility.

    +
    + +
    +
    +class gino.loader.CallableLoader(func)
    +

    Bases: gino.loader.Loader

    +

    Load the row by calling a specified function.

    +
    +
    Parameters
    +

    func – A callable() taking 2 positional arguments (row, context) +that will be called in do_load(), returning the loaded result.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to the given function.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    The result calling the given function, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ColumnLoader(column)
    +

    Bases: gino.loader.Loader

    +

    Load a given column in the row.

    +
    +
    Parameters
    +

    column – The column name as str, or a +Column instance to avoid name conflict.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – Not used.

    • +
    +
    +
    Returns
    +

    The value of the specified column, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.Loader
    +

    Bases: object

    +

    The abstract base class of loaders.

    +

    Loaders are used to load raw database rows into expected results. +GinoEngine will use the loader set on the loader value of +the execution_options(), for example:

    +
    from sqlalchemy import text, create_engine
    +from gino.loader import ColumnLoader
    +
    +e = await create_engine("postgresql://localhost/gino", strategy="gino")
    +q = text("SELECT now() as ts")
    +loader = ColumnLoader("ts")
    +ts = await e.first(q.execution_options(loader=loader))  # datetime
    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    Must be implemented in subclasses.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    Any result that the loader is supposed to return, followed by a boolean +value indicating if the result is distinct.

    +
    +
    +
    + +
    +
    +classmethod get(value)
    +

    Automatically create a loader based on the type of the given value.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    value type

    loader type

    tuple

    TupleLoader

    callable()

    CallableLoader

    Column, +Label

    ColumnLoader

    Model

    ModelLoader

    Alias

    AliasLoader

    Loader

    as is

    any other types

    ValueLoader

    +
    +
    Parameters
    +

    value – Any supported value above.

    +
    +
    Returns
    +

    A loader instance.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +property query
    +

    Generate a query from this loader.

    +

    This is an experimental feature, not all loaders support this.

    +
    +
    Returns
    +

    A query instance with the loader execution option set to self.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ModelLoader(model, *columns, **extras)
    +

    Bases: gino.loader.Loader

    +

    A loader that loads a row into a GINO model instance.

    +

    This loader generates an instance of the given model type and fills the instance +with attributes according to the other given parameters:

    +
      +
    • Load each column attribute listed in the given columns positional arguments.

    • +
    • If columns is not given, all defined columns of the model will be loaded.

    • +
    • For each keyword argument, its value will be used to generate a loader using +Loader.get(), and the loaded value will be setattr() to the model +instance under the name of the key.

    • +
    +

    For example, the simplest select and load:

    +
    sqlalchemy.select([User]).execution_options(loader=ModelLoader(User))
    +
    +
    +

    Select only the name column, and still load it into model instances:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, User.name)
    +)
    +
    +
    +

    This would also yield User instances, with all column attributes as None but +name.

    +

    Nest a ValueLoader:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, id=1)
    +)
    +
    +
    +

    1 is then converted into a ValueLoader and mocked the id attribute +of all returned User instances as 1.

    +

    Nest another ModelLoader:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company)
    +)
    +
    +
    +

    Likewise, Company is converted into a ModelLoader to load the +Company columns from the joined result, and the Company instances are set to +the company attribute of each User instance using setattr().

    +
    +
    Parameters
    +
      +
    • model – A subclass of Model to instantiate.

    • +
    • columns – A list of Column or str to +load, default is all the columns in the model.

    • +
    • extras – Additional attributes to load on the model instance.

    • +
    +
    +
    +
    +
    +property columns
    +
    + +
    +
    +distinct(*columns)
    +

    Configure this loader to reuse instances that have the same values of all the +give columns.

    +
    +
    Parameters
    +

    columns – Preferably Column instances.

    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    The model instance, followed by a boolean value indicating if the +result is distinct.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    Returns
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +load(*columns, **extras)
    +

    Update the loader with new rules.

    +

    After initialization, the rules of this loader can still be updated. This is +useful when using the model class as a shortcut of ModelLoader where +possible, chaining with a load() to initialize the rules, for example:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company.load('name'))
    +)
    +
    +
    +
    +
    Parameters
    +
      +
    • columns – If provided, replace the columns to load with the given ones.

    • +
    • extras – Update the loader with new extras.

    • +
    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +none_as_none(enabled=True)
    +

    Deprecated method for compatibility, does nothing.

    +
    + +
    +
    +on(on_clause)
    +

    Specify the on_clause for generating joined queries.

    +

    This is an experimental feature, used by get_from().

    +
    +
    Parameters
    +

    on_clause – An expression to feed into +join().

    +
    +
    Returns
    +

    self for chaining.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.TupleLoader(values)
    +

    Bases: gino.loader.Loader

    +

    Load multiple values into a tuple.

    +
    +
    Parameters
    +

    values – A tuple, each item is converted into a loader with +Loader.get().

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to sub-loaders.

    +
    +
    Parameters
    +
      +
    • row – A RowProxy instance.

    • +
    • context – A dict that is reused across all loaders in one query.

    • +
    +
    +
    Returns
    +

    A tuple with loaded results from all sub-loaders, followed by +True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ValueLoader(value)
    +

    Bases: gino.loader.Loader

    +

    A loader that always return the specified value.

    +
    +
    Parameters
    +

    value – The value to return on load.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    Parameters
    +
      +
    • row – Not used.

    • +
    • context – Not used.

    • +
    +
    +
    Returns
    +

    The given value, followed by True.

    +
    +
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.schema.html b/docs/en/master/reference/api/gino.schema.html new file mode 100644 index 0000000..dea0bc3 --- /dev/null +++ b/docs/en/master/reference/api/gino.schema.html @@ -0,0 +1,335 @@ + + + + + + + + gino.schema module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.schema module

    +
    +
    +class gino.schema.AsyncSchemaDropper(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    Bases: gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaDropper

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, drop_ok=False)
    +
    + +
    +
    +async visit_table(table, drop_ok=False, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaGenerator(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    Bases: gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaGenerator

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, create_ok=False)
    +
    + +
    +
    +async visit_table(table, create_ok=False, include_foreign_key_constraints=None, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaTypeMixin
    +

    Bases: object

    +
    +
    +async create_async(bind=None, checkfirst=False)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncVisitor
    +

    Bases: object

    +
    +
    +async traverse_single(obj, **kw)
    +
    + +
    + +
    +
    +class gino.schema.GinoSchemaVisitor(item)
    +

    Bases: object

    +
    +
    +async create(bind=None, *args, **kwargs)
    +
    + +
    +
    +async create_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    +
    +async drop(bind=None, *args, **kwargs)
    +
    + +
    +
    +async drop_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    + +
    +
    +gino.schema.patch_schema(db)
    +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.strategies.html b/docs/en/master/reference/api/gino.strategies.html new file mode 100644 index 0000000..f452cb9 --- /dev/null +++ b/docs/en/master/reference/api/gino.strategies.html @@ -0,0 +1,243 @@ + + + + + + + + gino.strategies module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.strategies module

    +
    +
    +class gino.strategies.GinoStrategy
    +

    Bases: sqlalchemy.engine.strategies.EngineStrategy

    +

    A SQLAlchemy engine strategy for GINO.

    +

    This strategy is initialized automatically as gino is imported.

    +

    If sqlalchemy.create_engine() uses strategy="gino", it will return a +Coroutine, and treat URL prefix postgresql:// or +postgres:// as postgresql+asyncpg://.

    +
    +
    +async create(name_or_url, loop=None, **kwargs)
    +

    Given arguments, returns a new Engine instance.

    +
    + +
    +
    +engine_cls
    +

    alias of gino.engine.GinoEngine

    +
    + +
    +
    +name = 'gino'
    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/api/gino.transaction.html b/docs/en/master/reference/api/gino.transaction.html new file mode 100644 index 0000000..a8cc1fd --- /dev/null +++ b/docs/en/master/reference/api/gino.transaction.html @@ -0,0 +1,339 @@ + + + + + + + + gino.transaction module - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.transaction module

    +
    +
    +class gino.transaction.GinoTransaction(conn, args, kwargs)
    +

    Bases: object

    +

    Represents an underlying database transaction and its connection, offering +methods to manage this transaction.

    +

    GinoTransaction is supposed to be created by either +gino.engine.GinoConnection.transaction(), or +gino.engine.GinoEngine.transaction(), or +gino.api.Gino.transaction(), shown as follows:

    +
    async with db.transaction() as tx:
    +    ...
    +
    +async with engine.transaction() as tx:
    +    ...
    +
    +async with conn.transaction() as tx:
    +    ...
    +
    +tx = await conn.transaction()
    +try:
    +    ...
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    When in use with asynchronous context manager, GinoTransaction +will be in managed mode, while the last example with await will put +the GinoTransaction in manual mode where you have to call +the commit() or rollback() to manually close the transaction.

    +

    In managed mode the transaction will be automatically committed or +rolled back on exiting the async with block depending on whether there +is an exception or not. Meanwhile, you can explicitly exit the transaction +early by raise_commit() or raise_rollback() which will raise +an internal exception managed by the asynchronous context manager and +interpreted as a commit or rollback action. In a nested transaction +situation, the two exit-early methods always close up the very transaction +which the two methods are referenced upon - all children transactions are +either committed or rolled back correspondingly, while no parent +transaction was ever touched. For example:

    +
    async with db.transaction() as tx1:
    +    async with db.transaction() as tx2:
    +        async with db.transaction() as tx3:
    +            tx2.raise_rollback()
    +            # Won't reach here
    +        # Won't reach here
    +    # Continues here with tx1, with both tx2 and tx3 rolled back.
    +
    +    # For PostgreSQL, tx1 can still be committed successfully because
    +    # tx2 and tx3 are just SAVEPOINTs in transaction tx1
    +
    +
    +
    +

    Tip

    +

    The internal exception raised from raise_commit() and +raise_rollback() is a subclass of BaseException, so +normal try ... except Exception: can’t trap the commit or rollback.

    +
    +
    +
    +async commit()
    +

    Only available in manual mode: manually commit this transaction.

    +
    + +
    +
    +property connection
    +

    Accesses to the GinoConnection of this +transaction. This is useful if when the transaction is started from +db or engine where the connection is implicitly acquired for +you together with the transaction.

    +
    + +
    +
    +raise_commit()
    +

    Only available in managed mode: skip rest of the code in this +transaction and commit immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    async with db.transaction() as tx:
    +    await user.update(age=64).apply()
    +    tx.raise_commit()
    +    await user.update(age=32).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +raise_rollback()
    +

    Only available in managed mode: skip rest of the code in this +transaction and rollback immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    assert user.age == 64  # assumption
    +
    +async with db.transaction() as tx:
    +    await user.update(age=32).apply()
    +    tx.raise_rollback()
    +    await user.update(age=128).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +property raw_transaction
    +

    Accesses to the underlying transaction object, whose type depends on +the dialect in use.

    +
    + +
    +
    +async rollback()
    +

    Only available in manual mode: manually rollback this transaction.

    +
    + +
    + +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/authors.html b/docs/en/master/reference/authors.html new file mode 100644 index 0000000..324c702 --- /dev/null +++ b/docs/en/master/reference/authors.html @@ -0,0 +1,221 @@ + + + + + + + + Credits - GINO 1.0.0rc3 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Credits

    +
    +

    Development Lead

    + +
    +
    +

    Maintainers

    + +
    +
    +

    Contributors

    + +

    Special thanks to my wife Daisy and her outsourcing company DecentFoX Studio, +for offering me the opportunity to build this project. We are open for global +software project outsourcing on Python, iOS and Android development.

    +
    +
    + + +
    +
    + + + +
    +
    + + +
    + EN +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/extensions.html b/docs/en/master/reference/extensions.html new file mode 100644 index 0000000..587283f --- /dev/null +++ b/docs/en/master/reference/extensions.html @@ -0,0 +1,227 @@ + + + + + + + + Extensions - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    + + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/extensions/sanic.html b/docs/en/master/reference/extensions/sanic.html new file mode 100644 index 0000000..815a491 --- /dev/null +++ b/docs/en/master/reference/extensions/sanic.html @@ -0,0 +1,338 @@ + + + + + + + + Sanic Support - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_SSL

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_SSL: if not set, None

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/extensions/starlette.html b/docs/en/master/reference/extensions/starlette.html new file mode 100644 index 0000000..ca1314d --- /dev/null +++ b/docs/en/master/reference/extensions/starlette.html @@ -0,0 +1,333 @@ + + + + + + + + Starlette Support - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    Note

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/extensions/tornado.html b/docs/en/master/reference/extensions/tornado.html new file mode 100644 index 0000000..56cc757 --- /dev/null +++ b/docs/en/master/reference/extensions/tornado.html @@ -0,0 +1,215 @@ + + + + + + + + Tornado Support - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/reference/history.html b/docs/en/master/reference/history.html new file mode 100644 index 0000000..e42d4bf --- /dev/null +++ b/docs/en/master/reference/history.html @@ -0,0 +1,929 @@ + + + + + + + + History - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    History

    +
    +

    GINO 1.1

    +
    +

    1.1.0 (pending)

    +
      +
    • Added baked query feature (#478 #659 #667)

    • +
    • Added Query.gino.execution_options shortcut (#659)

    • +
    • Added @db.declared_attr(with_table=True) (#659)

    • +
    • [Breaking] Empty object instead of None being returned for objects with values of all selected columns are None (#729)

    • +
    • Added MySQL support (#381 #685)

    • +
    • [Breaking] asyncpg is no longer installed as a dependency by default, install gino[pg] for the old behavior

    • +
    • Fixed multiple referenced connection stack in newly created coroutines (#747)

    • +
    +
    +
    +
    +

    GINO 1.0

    +
    +

    Migrating to GINO 1.0

    +

    GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you’re using one of them, you should install GINO 1.0 with extras:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +

    Extension Module

    Installation in GINO 1.0

    gino.ext.starlette

    pip install gino[starlette]

    gino.ext.aiohttp

    pip install gino[aiohttp]

    gino.ext.sanic

    pip install gino[sanic]

    gino.ext.tornado

    pip install gino[tornado]

    gino.ext.quart

    pip install gino[quart]

    +

    The new extension packages are backward-compatible, so there’s no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed gino[starlette]:

    +
    from gino.ext.starlette import Gino
    +
    +
    +

    GINO 1.0 switched to Poetry for package and +dependency management and started to use the +src layout. This shouldn’t +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process:

    +
      +
    • Source files are now located under src directory.

    • +
    • The src dist on PyPI does not include tests, docs and some other files due to +a limitation of Poetry.

    • +
    +
    +
    +

    1.0.1 (2020-06-08)

    +
      +
    • Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4)

    • +
    • Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672)

    • +
    • Multiple JSON property fixes (#661 #662 #695)

    • +
    • Fixed extension typing issue (#673 #674)

    • +
    • Fixed model override behavior (#694)

    • +
    • Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696)

    • +
    +
    +
    +

    1.0.0 (2020-04-26)

    +
      +
    • Switched to Poetry for package and dependency management.

    • +
    • [Breaking] Moved built-in extension modules to separate PyPI packages.

    • +
    • Switched to src layout.

    • +
    • Switched to black code style.

    • +
    • Better documentation.

    • +
    • [Breaking] none_as_none() is now always enabled.

    • +
    • Added representation method for engine.

    • +
    • Protected the URL instance fed to set_bind() from manipulation.

    • +
    • Replaced some assert with AssertionError (#258 #655)

    • +
    +
    +
    +
    +

    GINO 0.8

    +

    This is also version 1.0 release candidate.

    +
    +

    Migrating to GINO 0.8

    +
    +

    1. contextvars

    +

    We introduced aiocontextvars 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the enable_inherit() or +disable_inherit() calls, because they are the default behavior now thus +no longer exist. However, you’ll need to confirm that the event loop in use is +always created after importing gino or aiocontextvars, or the patch +won’t work correctly.

    +

    There is nothing to worry about in Python 3.7.

    +
    +
    +

    2. none_as_none

    +

    When GINO tries to load a row with all NULL values into an instance, it +will now by default return None instead of an instance with all None +attributes. To recover the default behavior of 0.7, please specify +none_as_none(False) in affected model loader.

    +

    This is especially applicable to relationship sub-loaders - if the sub-loader +found it all NULL, no instance will be set to parent instance. For +example:

    +
    child = await Child.load(parent=Parent).query.gino.first()
    +
    +
    +

    If child.parent_id is NULL in database, then the child instance +won’t be called with any setattr(child, 'parent', ...) at all. (If you need +child.parent == None in this case, consider setting default value +parent = None in child model.)

    +

    Please note, it is deprecated to disable none_as_none, and disabling will +be removed in GINO 1.0.

    +
    +
    +
    +

    0.8.7 (2020-04-19)

    +
      +
    • Improved error handling when attribute names collide (Contributed by Reskov in #637 #638)

    • +
    • Fixed with_bind usability in aiohttp extension (#518)

    • +
    +
    +
    +

    0.8.6 (2020-02-10)

    +
      +
    • Fixed JSONPathType bind processor for asyncpg (#609)

    • +
    • Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600)

    • +
    • Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629)

    • +
    • Added distinct() to Alias (#628)

    • +
    +
    +
    +

    0.8.5 (2019-11-19)

    +
      +
    • Improved support for __tablename__ in declared_attr (Contributed by Roald Storm in #592)

    • +
    +
    +
    +

    0.8.4 (2019-11-09)

    +
      +
    • Better loader support for models in subqueries (#573 #585)

    • +
    • Allowed __tablename__ to be a declared_attr (#579 #582)

    • +
    • Fixed Sanic 19.9.0 compatibility (#569)

    • +
    • Added one() and one_or_none() (Contributed by Ilaï Deutel in #577)

    • +
    • Improved Starleet extension compatibility (Contributed by Jim O’Brien in #538)

    • +
    • Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533)

    • +
    • Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520)

    • +
    • Fixed Grammar (Contributed by Simeon J Morgan in #504)

    • +
    +
    +
    +

    0.8.3 (2019-06-06)

    +
      +
    • Fixed deprecated warnings in asyncpg dialect and aiohttp (#425)

    • +
    • Customizable db attribute name in aiohttp app instance (#457)

    • +
    • Added Starlette support (#486)

    • +
    +
    +
    +

    0.8.2 (2019-03-07)

    +
      +
    • Added exception for unknown JSON properties (#406 #408)

    • +
    • Supported Quart 0.7 (#411)

    • +
    • Accepted kwargs for db init in extensions (#407 #427)

    • +
    • Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440)

    • +
    • Added NullPool (#437 #441)

    • +
    • Unpinned dependency versions (#447)

    • +
    • Added support for SQLAlchemy 1.3 (#433 #451)

    • +
    +
    +
    +

    0.8.1 (2018-12-08)

    +
      +
    • Alias supported Label (#365)

    • +
    • Docs update (#308, 4c59ad, #401 by Pascal van Kooten)

    • +
    • Version requirement for SQLAlchemy is updated to >=1.2 (#378 #382)

    • +
    • Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) +* And all other extensions (#395)

    • +
    • Supported Tornado 5 (#396, also thanks to Vladimir Goncharov)

    • +
    • Fixed custom JSON/JSONB type support (#402 #403)

    • +
    +

    (Most fixes done by Tony Wang)

    +
    +
    +

    0.8.0 (2018-10-16)

    +
      +
    • Welcome Tony Wang to the maintenance team (#335)

    • +
    • Allowed custom column names (#261 #297)

    • +
    • Allowed column instance in model.load() (Contributed by Jekel in #323)

    • +
    • [Breaking] Upgraded to aiocontextvars 0.2.0 (#333)

    • +
    • Fixed bug that the same empty stack is shared between sub-tasks (#313 #334)

    • +
    • [Breaking] Made none_as_none() the default behavior (#351)

    • +
    • Bug fixes and docs update

    • +
    +
    +
    +
    +

    GINO 0.7

    +

    This is also version 1.0 beta 3.

    +
    +

    0.7.7 (2018-12-08)

    +
      +
    • Backported fix for custom JSON/JSONB type support (#402 #403)

    • +
    +
    +
    +

    0.7.6 (2018-08-26)

    +
      +
    • Updated library support (Contributed by Tony Wang in #275 #309)

    • +
    • Added none_as_none() (#281 #282)

    • +
    • Added ARRAY alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289)

    • +
    • Added Model.lookup() to prevent updating whole table without primary key (#287 #288)

    • +
    • Added DB_ECHO in extension options (Contributed by Mykyta Holubakha in #298)

    • +
    • Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305)

    • +
    • Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304)

    • +
    • Fixed to raise UninitializedError if bind is None (Contributed by Tony Wang in #307 #310)

    • +
    +
    +
    +

    0.7.5 (2018-07-26)

    +
      +
    • Added friendly error message when using abstract models by mistake (#224)

    • +
    • Supported Python 3.7 (Contributed by Tony Wang in #265)

    • +
    • Updated documentation

    • +
    • Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280)

    • +
    +
    +
    +

    0.7.4 (2018-06-10)

    +
      +
    • Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228)

    • +
    • Added Quart support (#213)

    • +
    • Fixed Tornado options parsing (#231)

    • +
    • Improved coding style and test coverage

    • +
    +
    +
    +

    0.7.3 (2018-05-19)

    +
      +
    • Fix for failing binary type (#225)

    • +
    +
    +
    +

    0.7.2 (2018-05-15)

    +
      +
    • Added prepared statement support (#14)

    • +
    • Added dsn in extension config (Contributed by Yurii Shtrikker in #215)

    • +
    +
    +
    +

    0.7.1 (2018-05-03)

    +
      +
    • Added support for inline model constraints (Contributed by Kinware in #198)

    • +
    • Added docs and tests for using SSL (#202)

    • +
    • Added declared_attr (#204)

    • +
    • Allowed ModelLoader passively load partial model (#216)

    • +
    +
    +
    +

    0.7.0 (2018-04-18)

    +
      +
    • Added Python 3.5 support (#187)

    • +
    • Added support to use dict as ident for Model.get (#192)

    • +
    • Added result loader (partial relationship support) (#13)

    • +
    • Added documentation on relationship and transaction (#146)

    • +
    +
    +
    +
    +

    GINO 0.6

    +

    This is also version 1.0 beta 2.

    +
    +

    Migrating to GINO 0.6

    +
    +

    1. Task Local

    +

    We created a new Python package aiocontextvars from previous local.py. If +you made use of the task local features, you should install this package.

    +

    Previous gino.enable_task_local() and gino.disable_task_local() are +replaced by aiocontextvars.enable_inherit() and +aiocontextvars.disable_inherit(). However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars by default offers task +local even without enable_inherit(), which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars is installed.

    +

    There is no gino.get_local() and gino.reset_local() relevant in +aiocontextvars. The similar thing is aiocontextvars.ContextVar instance +through its get(), set() and delete() methods.

    +

    Previous gino.is_local_root() is now +not aiocontextvars.Context.current().inherited.

    +
    +
    +

    2. Engine

    +

    GINO 0.6 hides asyncpg.Pool behind the new SQLAlchemy-alike +gino.GinoEngine. Instead of doing this in 0.5:

    +
    async with db.create_pool('postgresql://...') as pool:
    +    # your code here
    +
    +
    +

    You should change it to this in 0.6:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +

    This equals to:

    +
    engine = await gino.create_engine('postgresql://...')
    +db.bind = engine
    +try:
    +    # your code here
    +finally:
    +    db.bind = None
    +    await engine.close()
    +
    +
    +

    Or:

    +
    engine = await db.set_bind('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Or even this:

    +
    db = await gino.Gino('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Choose whichever suits you the best.

    +

    Obviously GinoEngine doesn’t provide asyncpg.Pool methods directly any +longer, but you can get the underlying asyncpg.Pool object through +engine.raw_pool property.

    +

    GinoPool.get_current_connection() is now changed to current_connection +property on GinoEngine instances to support multiple engines.

    +

    GinoPool.execution_option is gone, instead update_execution_options() +on GinoEngine instance is available.

    +

    GinoPool().metadata is gone, dialect is still available.

    +

    GinoPool.release() is removed in GinoEngine and Gino, the +release() method on GinoConnection object should be used instead.

    +

    These methods exist both in 0.5 GinoPool and 0.6 GinoEngine: +close(), acquire(), all(), first(), scalar(), status().

    +
    +
    +

    3. GinoConnection

    +

    Similarly, GinoConnection in 0.6 is no longer a subclass of +asyncpg.Connection, instead it has a asyncpg.Connection instance, +accessable through GinoConnection.raw_connection property.

    +

    GinoConnection.metadata is deleted in 0.6, while dialect remained.

    +

    GinoConnection.execution_options() is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior.

    +

    GinoConnection.release() is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +permanent=False to remain its previous behavior.

    +

    And all(), first(), scalar(), status(), iterate(), +transaction() remained in 0.6.

    +
    +
    +

    4. Query API

    +

    All five query APIs all(), first(), scalar(), status(), +iterate() now accept the same parameters as SQLAlchemy execute(), +meaning they accept raw SQL text, or multiple sets of parameters for +“executemany”. Please note, if the parameters are recognized as “executemany”, +none of the methods will return anything. Meanwhile, they no longer accept the +parameter bind if they did. Just use the API on the GinoEngine or +GinoConnection object instead.

    +
    +
    +

    5. Transaction

    +

    Transaction interface is rewritten. Now in 0.6, a GinoTransaction object is +provided consistently from all 3 methods:

    +
    async with db.transaction() as tx:
    +    # within transaction
    +
    +async with engine.transaction() as tx:
    +    # within transaction
    +
    +async with engine.acquire() as conn:
    +    async with conn.transaction() as tx:
    +        # within transaction
    +
    +
    +

    And different usage with await:

    +
    tx = await db.transaction()
    +try:
    +    # within transaction
    +    await tx.commit()
    +except:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    The GinoConnection object is available at tx.connection, while +underlying transaction object from database driver is available at +tx.transaction - for asyncpg it is an asyncpg.transaction.Transaction +object.

    +
    +
    +
    +

    0.6.6 (2018-05-18)

    +
      +
    • Backported a fix for failing binary type (#225)

    • +
    +
    +
    +

    0.6.5 (2018-04-18)

    +
      +
    • Abandoned 0.6.4 and keep 0.6.x stable

    • +
    • Backported doc for transaction

    • +
    +
    +
    +

    0.6.4 (2018-04-16)

    +

    Abandoned version, please use 0.7.0 instead.

    +
    +
    +

    0.6.3 (2018-04-08)

    +
      +
    • Added aiohttp support

    • +
    • Added support for calling create() on model instances (Contributed by Kinware in #178 #180)

    • +
    • Fixed get() by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184)

    • +
    +
    +
    +

    0.6.2 (2018-03-24)

    +
      +
    • Fixed SQLAlchemy prefetch issue (#141)

    • +
    • Fixed issue that mixin class on Model not working (#174)

    • +
    • Added more documentation (Thanks Olaf Conradi for reviewing)

    • +
    +
    +
    +

    0.6.1 (2018-03-18)

    +
      +
    • Fixed create and drop for Enum type (#160)

    • +
    • A bit more documentation (#159)

    • +
    +
    +
    +

    0.6.0 (2018-03-14)

    +
      +
    • [Breaking] API Refactored, Pool replaced with Engine

      +
        +
      • New API Engine replaced asyncpg Pool (#59)

      • +
      • Supported different dialects, theoretically

      • +
      • Used aiocontextvars instead of builtin task local (#89)

      • +
      +
    • +
    • [Breaking] Fixed query API with multiparams (executemany) to return correctly (#20)

    • +
    • [Breaking] The query methods no longer accept the parameter bind

    • +
    • [Breaking] Gino no longer exposes postgresql types

    • +
    • Added echo on engine (#142)

    • +
    • Added tests to cover 80% of code

    • +
    • Added gino extension on SchemaItem for create_all and so on (#76 #106)

    • +
    • Added gino extension on model classes for create() or drop()

    • +
    • Added _update_request_cls on CRUDModel (#147)

    • +
    • Rewrote the documentation (#146)

    • +
    +
    +
    +
    +

    GINO 0.5

    +

    This is also version 1.0 beta 1.

    +
    +

    0.5.8 (2018-02-14)

    +
      +
    • Preparing for 0.6.0 which will be a breaking release

    • +
    • Fixed wrong value of Enum in creation (Contributed by Sergey Kovalev in #126)

    • +
    +
    +
    +

    0.5.7 (2017-11-24)

    +

    This is an emergency fix for 0.5.6.

    +
      +
    • Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114)

    • +
    • Added Model.outerjoin

    • +
    +
    +
    +

    0.5.6 (2017-11-23)

    +
      +
    • Changed to use unnamed statement when possible (#80 #90)

    • +
    • Added more example (Contributed by Kentoseth in #109)

    • +
    • Added Model.join and made Model selectable (Contributed by Ádám Barancsuk in #112 #113)

    • +
    +
    +
    +

    0.5.5 (2017-10-18)

    +
      +
    • Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87)

    • +
    • Added ability to reset local storage (#84)

    • +
    • Fixed bug in JSON property update

    • +
    • Added update chaining feature

    • +
    +
    +
    +

    0.5.4 (2017-10-04)

    +
      +
    • Updated example (Contributed by Kinware in #75)

    • +
    • Added Model.insert (Contributed by Neal Wang in #63)

    • +
    • Fixed issue that non-lazy acquiring fails dirty (#79)

    • +
    +
    +
    +

    0.5.3 (2017-09-23)

    +
      +
    • Fixed no module named cutils error (Contributed by Vladimir Goncharov in #73)

    • +
    +
    +
    +

    0.5.2 (2017-09-10)

    +
      +
    • Added missing driver name on dialect (#67)

    • +
    • Fixed dialect to support native decimal type (#67)

    • +
    +
    +
    +

    0.5.1 (2017-09-09)

    +

    This is an emergency fix for 0.5.0.

    +
      +
    • Reverted the extension, back to pure Python (#60)

    • +
    • Used SQLAlchemy RowProxy

    • +
    • Added first_or_404

    • +
    • Fixed bug that GinoPool cannot be inherited

    • +
    +
    +
    +

    0.5.0 (2017-09-03)

    +
      +
    • [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten

      +
        +
      • Extracted CRUD operations

      • +
      • Core operations are moved to dialect and execution context

      • +
      • Removed guess_model, switched to explicit execution options

      • +
      • Turned timeout parameter to an execution option

      • +
      • Extracted pool, connection and api from asyncpg_delegate

      • +
      +
    • +
    • Added support for SQLAlchemy execution options, and a few custom options

    • +
    • [Breaking] Made Model.select return rows by default (#39)

    • +
    • Moved get_or_404 to extensions (#38)

    • +
    • Added iterator on model classes (#43)

    • +
    • Added Tornado extension (Contributed by Vladimir Goncharov)

    • +
    • Added Model.to_dict (#47)

    • +
    • Added an extension module to update asyncpg.Record with processed results

    • +
    +
    +
    +
    +

    Early Development Releases

    +

    Considered as alpha releases.

    +
    +

    0.4.1 (2017-08-20)

    +
      +
    • Support select on model instance

    • +
    +
    +
    +

    0.4.0 (2017-08-15)

    +
      +
    • Made get_or_404 more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31)

    • +
    • Delegated sqlalchemy.__all__ (Contributed by Neal Wang in #10 #33)

    • +
    • [Breaking] Rewrote JSON/JSONB support (#29)

    • +
    • Added lazy parameter on db.acquire (Contributed by Binghan Li in #32)

    • +
    • Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34)

    • +
    • Fixed iterate API to be compatible with asyncpg (#32)

    • +
    • Unified exceptions

    • +
    • [Breaking] Changed update API (#29)

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.3.0 (2017-08-07)

    +
      +
    • Supported __table_args__ (#12)

    • +
    • Introduced task local to manage connection in context (#19)

    • +
    • Added query.gino extension for in-place execution

    • +
    • Refreshed README (#3)

    • +
    • Adopted PEP 487 (Contributed by Tony Wang in #17 #27)

    • +
    • Used weakref on __model__ of table and query (Contributed by Tony Wang)

    • +
    • Delegated asyncpg timeout parameter (Contributed by Neal Wang in #16 #22)

    • +
    +
    +
    +

    0.2.3 (2017-08-04)

    +
      +
    • Supported any primary key (Contributed by Tony Wang in #11)

    • +
    +
    +
    +

    0.2.2 (2017-08-02)

    +
      +
    • Supported SQLAlchemy result processor

    • +
    • Added rich support on JSON/JSONB

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.2.1 (2017-07-28)

    +
      +
    • Added update and delete API

    • +
    +
    +
    +

    0.2.0 (2017-07-28)

    +
      +
    • Changed API, no longer reuses asyncpg API

    • +
    +
    +
    +

    0.1.1 (2017-07-25)

    +
      +
    • Added db.bind

    • +
    • API changed: parameter conn renamed to optional bind

    • +
    • Delegated asyncpg Pool with db.create_pool

    • +
    • Internal enhancement and bug fixes

    • +
    +
    +
    +

    0.1.0 (2017-07-21)

    +
      +
    • First release on PyPI.

    • +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/search.html b/docs/en/master/search.html new file mode 100644 index 0000000..2500d6c --- /dev/null +++ b/docs/en/master/search.html @@ -0,0 +1 @@ +

    search.html

    \ No newline at end of file diff --git a/docs/en/master/searchindex.js b/docs/en/master/searchindex.js new file mode 100644 index 0000000..9f7c997 --- /dev/null +++ b/docs/en/master/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["explanation","explanation/async","explanation/engine","explanation/sa20","explanation/why","how-to","how-to/alembic","how-to/bakery","how-to/contributing","how-to/crud","how-to/faq","how-to/json-props","how-to/loaders","how-to/pool","how-to/schema","how-to/transaction","index","reference","reference/api","reference/api/gino","reference/api/gino.aiocontextvars","reference/api/gino.api","reference/api/gino.bakery","reference/api/gino.crud","reference/api/gino.declarative","reference/api/gino.dialects","reference/api/gino.dialects.aiomysql","reference/api/gino.dialects.asyncpg","reference/api/gino.dialects.base","reference/api/gino.engine","reference/api/gino.exceptions","reference/api/gino.ext","reference/api/gino.json_support","reference/api/gino.loader","reference/api/gino.schema","reference/api/gino.strategies","reference/api/gino.transaction","reference/extensions","reference/extensions/sanic","reference/extensions/starlette","reference/extensions/tornado","reference/history","tutorials","tutorials/announcement","tutorials/fastapi","tutorials/tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["explanation.rst","explanation/async.rst","explanation/engine.rst","explanation/sa20.rst","explanation/why.rst","how-to.rst","how-to/alembic.rst","how-to/bakery.rst","how-to/contributing.rst","how-to/crud.rst","how-to/faq.rst","how-to/json-props.rst","how-to/loaders.rst","how-to/pool.rst","how-to/schema.rst","how-to/transaction.rst","index.rst","reference.rst","reference/api.rst","reference/api/gino.rst","reference/api/gino.aiocontextvars.rst","reference/api/gino.api.rst","reference/api/gino.bakery.rst","reference/api/gino.crud.rst","reference/api/gino.declarative.rst","reference/api/gino.dialects.rst","reference/api/gino.dialects.aiomysql.rst","reference/api/gino.dialects.asyncpg.rst","reference/api/gino.dialects.base.rst","reference/api/gino.engine.rst","reference/api/gino.exceptions.rst","reference/api/gino.ext.rst","reference/api/gino.json_support.rst","reference/api/gino.loader.rst","reference/api/gino.schema.rst","reference/api/gino.strategies.rst","reference/api/gino.transaction.rst","reference/extensions.rst","reference/extensions/sanic.rst","reference/extensions/starlette.rst","reference/extensions/tornado.rst","reference/history.rst","tutorials.rst","tutorials/announcement.rst","tutorials/fastapi.rst","tutorials/tutorial.rst"],objects:{"":[[19,0,0,"-","gino"]],"gino.aiocontextvars":[[20,1,1,"","patch_asyncio"]],"gino.api":[[21,2,1,"","Gino"],[21,2,1,"","GinoExecutor"]],"gino.api.Gino":[[21,3,1,"","Model"],[21,4,1,"","acquire"],[21,4,1,"","all"],[21,4,1,"","bake"],[21,3,1,"","bakery"],[21,3,1,"","bind"],[21,4,1,"","compile"],[21,4,1,"","first"],[21,4,1,"","iterate"],[21,5,1,"","model_base_classes"],[21,5,1,"","no_delegate"],[21,4,1,"","one"],[21,4,1,"","one_or_none"],[21,4,1,"","pop_bind"],[21,5,1,"","query_executor"],[21,4,1,"","scalar"],[21,5,1,"","schema_visitor"],[21,4,1,"","set_bind"],[21,4,1,"","status"],[21,4,1,"","transaction"],[21,4,1,"","with_bind"]],"gino.api.GinoExecutor":[[21,4,1,"","all"],[21,4,1,"","execution_options"],[21,4,1,"","first"],[21,4,1,"","iterate"],[21,4,1,"","load"],[21,4,1,"","model"],[21,4,1,"","one"],[21,4,1,"","one_or_none"],[21,3,1,"","query"],[21,4,1,"","return_model"],[21,4,1,"","scalar"],[21,4,1,"","status"],[21,4,1,"","timeout"]],"gino.bakery":[[22,2,1,"","BakedQuery"],[22,2,1,"","Bakery"]],"gino.bakery.BakedQuery":[[22,3,1,"","bind"],[22,3,1,"","compiled_sql"],[22,4,1,"","execution_options"],[22,4,1,"","get"],[22,3,1,"","query"],[22,3,1,"","sql"]],"gino.bakery.Bakery":[[22,4,1,"","bake"],[22,5,1,"","query_cls"]],"gino.crud":[[23,2,1,"","Alias"],[23,2,1,"","CRUDModel"],[23,2,1,"","QueryModel"],[23,2,1,"","UpdateRequest"]],"gino.crud.Alias":[[23,4,1,"","distinct"],[23,4,1,"","load"],[23,4,1,"","on"]],"gino.crud.CRUDModel":[[23,4,1,"","alias"],[23,4,1,"","append_where_primary_key"],[23,4,1,"","create"],[23,5,1,"","delete"],[23,4,1,"","distinct"],[23,4,1,"","get"],[23,4,1,"","in_query"],[23,4,1,"","load"],[23,4,1,"","lookup"],[23,4,1,"","none_as_none"],[23,4,1,"","on"],[23,5,1,"","query"],[23,4,1,"","select"],[23,4,1,"","to_dict"],[23,5,1,"","update"]],"gino.crud.UpdateRequest":[[23,4,1,"","apply"],[23,4,1,"","update"]],"gino.declarative":[[24,2,1,"","ColumnAttribute"],[24,2,1,"","InvertDict"],[24,2,1,"","Model"],[24,1,1,"","declarative_base"],[24,1,1,"","declared_attr"]],"gino.declarative.InvertDict":[[24,4,1,"","invert_get"]],"gino.dialects":[[26,0,0,"-","aiomysql"],[27,0,0,"-","asyncpg"],[28,0,0,"-","base"]],"gino.dialects.aiomysql":[[26,2,1,"","AiomysqlDBAPI"],[26,2,1,"","AiomysqlDialect"],[26,2,1,"","AiomysqlExecutionContext"],[26,2,1,"","AiomysqlIterator"],[26,2,1,"","AsyncEnum"],[26,2,1,"","DBAPICursor"],[26,2,1,"","GinoNullType"],[26,2,1,"","Pool"],[26,2,1,"","Transaction"]],"gino.dialects.aiomysql.AiomysqlDBAPI":[[26,5,1,"","paramstyle"]],"gino.dialects.aiomysql.AiomysqlDialect":[[26,5,1,"","colspecs"],[26,5,1,"","cursor_cls"],[26,5,1,"","dbapi_class"],[26,5,1,"","driver"],[26,5,1,"","execution_ctx_cls"],[26,4,1,"","get_isolation_level"],[26,4,1,"","has_table"],[26,5,1,"","init_kwargs"],[26,4,1,"","init_pool"],[26,4,1,"","on_connect"],[26,5,1,"","postfetch_lastrowid"],[26,4,1,"","set_isolation_level"],[26,5,1,"","statement_compiler"],[26,5,1,"","support_prepare"],[26,5,1,"","support_returning"],[26,5,1,"","supports_native_decimal"],[26,4,1,"","transaction"]],"gino.dialects.aiomysql.AiomysqlExecutionContext":[[26,4,1,"","get_affected_rows"],[26,4,1,"","get_lastrowid"]],"gino.dialects.aiomysql.AiomysqlIterator":[[26,4,1,"","forward"],[26,4,1,"","many"],[26,4,1,"","next"]],"gino.dialects.aiomysql.AsyncEnum":[[26,4,1,"","create_async"],[26,4,1,"","drop_async"]],"gino.dialects.aiomysql.DBAPICursor":[[26,4,1,"","async_execute"],[26,3,1,"","description"],[26,4,1,"","execute_baked"],[26,4,1,"","get_statusmsg"],[26,4,1,"","iterate"],[26,4,1,"","prepare"]],"gino.dialects.aiomysql.GinoNullType":[[26,4,1,"","result_processor"]],"gino.dialects.aiomysql.Pool":[[26,4,1,"","acquire"],[26,4,1,"","close"],[26,3,1,"","raw_pool"],[26,4,1,"","release"],[26,4,1,"","repr"]],"gino.dialects.aiomysql.Transaction":[[26,4,1,"","begin"],[26,4,1,"","commit"],[26,3,1,"","raw_transaction"],[26,4,1,"","rollback"]],"gino.dialects.asyncpg":[[27,2,1,"","AsyncEnum"],[27,2,1,"","AsyncpgCompiler"],[27,2,1,"","AsyncpgCursor"],[27,2,1,"","AsyncpgDBAPI"],[27,2,1,"","AsyncpgDialect"],[27,2,1,"","AsyncpgExecutionContext"],[27,2,1,"","AsyncpgIterator"],[27,2,1,"","AsyncpgJSONPathType"],[27,2,1,"","DBAPICursor"],[27,2,1,"","GinoNullType"],[27,2,1,"","NullPool"],[27,2,1,"","Pool"],[27,2,1,"","PreparedStatement"],[27,2,1,"","Transaction"]],"gino.dialects.asyncpg.AsyncEnum":[[27,4,1,"","create_async"],[27,4,1,"","drop_async"]],"gino.dialects.asyncpg.AsyncpgCompiler":[[27,3,1,"","bindtemplate"]],"gino.dialects.asyncpg.AsyncpgCursor":[[27,4,1,"","forward"],[27,4,1,"","many"],[27,4,1,"","next"]],"gino.dialects.asyncpg.AsyncpgDBAPI":[[27,5,1,"","Error"]],"gino.dialects.asyncpg.AsyncpgDialect":[[27,5,1,"","colspecs"],[27,5,1,"","cursor_cls"],[27,5,1,"","dbapi_class"],[27,5,1,"","driver"],[27,5,1,"","execution_ctx_cls"],[27,4,1,"","get_isolation_level"],[27,4,1,"","has_schema"],[27,4,1,"","has_sequence"],[27,4,1,"","has_table"],[27,4,1,"","has_type"],[27,5,1,"","init_kwargs"],[27,4,1,"","init_pool"],[27,4,1,"","on_connect"],[27,4,1,"","set_isolation_level"],[27,5,1,"","statement_compiler"],[27,5,1,"","supports_native_decimal"],[27,4,1,"","transaction"]],"gino.dialects.asyncpg.AsyncpgJSONPathType":[[27,4,1,"","bind_processor"]],"gino.dialects.asyncpg.DBAPICursor":[[27,4,1,"","async_execute"],[27,3,1,"","description"],[27,4,1,"","execute_baked"],[27,4,1,"","get_statusmsg"],[27,4,1,"","prepare"]],"gino.dialects.asyncpg.GinoNullType":[[27,4,1,"","result_processor"]],"gino.dialects.asyncpg.NullPool":[[27,4,1,"","acquire"],[27,4,1,"","close"],[27,3,1,"","raw_pool"],[27,4,1,"","release"],[27,4,1,"","repr"]],"gino.dialects.asyncpg.Pool":[[27,4,1,"","acquire"],[27,4,1,"","close"],[27,3,1,"","raw_pool"],[27,4,1,"","release"],[27,4,1,"","repr"]],"gino.dialects.asyncpg.Transaction":[[27,4,1,"","begin"],[27,4,1,"","commit"],[27,3,1,"","raw_transaction"],[27,4,1,"","rollback"]],"gino.dialects.base":[[28,2,1,"","AsyncDialectMixin"],[28,2,1,"","BaseDBAPI"],[28,2,1,"","Cursor"],[28,2,1,"","DBAPICursor"],[28,2,1,"","ExecutionContextOverride"],[28,2,1,"","Pool"],[28,2,1,"","PreparedStatement"],[28,2,1,"","Transaction"]],"gino.dialects.base.AsyncDialectMixin":[[28,4,1,"","compile"],[28,5,1,"","cursor_cls"],[28,4,1,"","dbapi"],[28,5,1,"","dbapi_class"],[28,4,1,"","init_pool"],[28,5,1,"","support_prepare"],[28,5,1,"","support_returning"],[28,4,1,"","transaction"]],"gino.dialects.base.BaseDBAPI":[[28,4,1,"","Binary"],[28,5,1,"","Error"],[28,5,1,"","paramstyle"]],"gino.dialects.base.Cursor":[[28,4,1,"","forward"],[28,4,1,"","many"],[28,4,1,"","next"]],"gino.dialects.base.DBAPICursor":[[28,4,1,"","async_execute"],[28,3,1,"","description"],[28,4,1,"","execute"],[28,4,1,"","execute_baked"],[28,4,1,"","executemany"],[28,4,1,"","get_statusmsg"],[28,4,1,"","prepare"]],"gino.dialects.base.ExecutionContextOverride":[[28,5,1,"","baked_query"],[28,4,1,"","get_affected_rows"],[28,4,1,"","get_lastrowid"],[28,4,1,"","get_result_proxy"],[28,5,1,"","loader"],[28,5,1,"","model"],[28,4,1,"","process_rows"],[28,5,1,"","return_model"],[28,5,1,"","timeout"]],"gino.dialects.base.Pool":[[28,4,1,"","acquire"],[28,4,1,"","close"],[28,3,1,"","raw_pool"],[28,4,1,"","release"],[28,4,1,"","repr"]],"gino.dialects.base.PreparedStatement":[[28,4,1,"","all"],[28,4,1,"","first"],[28,4,1,"","iterate"],[28,4,1,"","scalar"],[28,4,1,"","status"]],"gino.dialects.base.Transaction":[[28,4,1,"","begin"],[28,4,1,"","commit"],[28,3,1,"","raw_transaction"],[28,4,1,"","rollback"]],"gino.engine":[[29,2,1,"","GinoConnection"],[29,2,1,"","GinoEngine"]],"gino.engine.GinoConnection":[[29,4,1,"","all"],[29,3,1,"","dialect"],[29,4,1,"","execution_options"],[29,4,1,"","first"],[29,4,1,"","get_raw_connection"],[29,4,1,"","iterate"],[29,4,1,"","one"],[29,4,1,"","one_or_none"],[29,4,1,"","prepare"],[29,3,1,"","raw_connection"],[29,4,1,"","release"],[29,4,1,"","scalar"],[29,5,1,"","schema_for_object"],[29,4,1,"","status"],[29,4,1,"","transaction"]],"gino.engine.GinoEngine":[[29,4,1,"","acquire"],[29,4,1,"","all"],[29,4,1,"","close"],[29,4,1,"","compile"],[29,5,1,"","connection_cls"],[29,3,1,"","current_connection"],[29,3,1,"","dialect"],[29,4,1,"","first"],[29,4,1,"","iterate"],[29,4,1,"","one"],[29,4,1,"","one_or_none"],[29,3,1,"","raw_pool"],[29,4,1,"","repr"],[29,4,1,"","scalar"],[29,4,1,"","status"],[29,4,1,"","transaction"],[29,4,1,"","update_execution_options"]],"gino.exceptions":[[30,6,1,"","GinoException"],[30,6,1,"","InitializedError"],[30,6,1,"","MultipleResultsFound"],[30,6,1,"","NoResultFound"],[30,6,1,"","NoSuchRowError"],[30,6,1,"","UninitializedError"],[30,6,1,"","UnknownJSONPropertyError"]],"gino.json_support":[[32,2,1,"","ArrayProperty"],[32,2,1,"","BooleanProperty"],[32,2,1,"","DateTimeProperty"],[32,2,1,"","IntegerProperty"],[32,2,1,"","JSONProperty"],[32,2,1,"","ObjectProperty"],[32,2,1,"","StringProperty"]],"gino.json_support.ArrayProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"]],"gino.json_support.BooleanProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","make_expression"]],"gino.json_support.DateTimeProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","make_expression"]],"gino.json_support.IntegerProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","make_expression"]],"gino.json_support.JSONProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","get_profile"],[32,4,1,"","make_expression"],[32,4,1,"","reload"],[32,4,1,"","save"]],"gino.json_support.ObjectProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"]],"gino.json_support.StringProperty":[[32,4,1,"","make_expression"]],"gino.loader":[[33,2,1,"","AliasLoader"],[33,2,1,"","CallableLoader"],[33,2,1,"","ColumnLoader"],[33,2,1,"","Loader"],[33,2,1,"","ModelLoader"],[33,2,1,"","TupleLoader"],[33,2,1,"","ValueLoader"]],"gino.loader.CallableLoader":[[33,4,1,"","do_load"]],"gino.loader.ColumnLoader":[[33,4,1,"","do_load"]],"gino.loader.Loader":[[33,4,1,"","do_load"],[33,4,1,"","get"],[33,4,1,"","get_columns"],[33,4,1,"","get_from"],[33,3,1,"","query"]],"gino.loader.ModelLoader":[[33,3,1,"","columns"],[33,4,1,"","distinct"],[33,4,1,"","do_load"],[33,4,1,"","get_columns"],[33,4,1,"","get_from"],[33,4,1,"","load"],[33,4,1,"","none_as_none"],[33,4,1,"","on"]],"gino.loader.TupleLoader":[[33,4,1,"","do_load"]],"gino.loader.ValueLoader":[[33,4,1,"","do_load"]],"gino.schema":[[34,2,1,"","AsyncSchemaDropper"],[34,2,1,"","AsyncSchemaGenerator"],[34,2,1,"","AsyncSchemaTypeMixin"],[34,2,1,"","AsyncVisitor"],[34,2,1,"","GinoSchemaVisitor"],[34,1,1,"","patch_schema"]],"gino.schema.AsyncSchemaDropper":[[34,4,1,"","visit_foreign_key_constraint"],[34,4,1,"","visit_index"],[34,4,1,"","visit_metadata"],[34,4,1,"","visit_sequence"],[34,4,1,"","visit_table"]],"gino.schema.AsyncSchemaGenerator":[[34,4,1,"","visit_foreign_key_constraint"],[34,4,1,"","visit_index"],[34,4,1,"","visit_metadata"],[34,4,1,"","visit_sequence"],[34,4,1,"","visit_table"]],"gino.schema.AsyncSchemaTypeMixin":[[34,4,1,"","create_async"],[34,4,1,"","drop_async"]],"gino.schema.AsyncVisitor":[[34,4,1,"","traverse_single"]],"gino.schema.GinoSchemaVisitor":[[34,4,1,"","create"],[34,4,1,"","create_all"],[34,4,1,"","drop"],[34,4,1,"","drop_all"]],"gino.strategies":[[35,2,1,"","GinoStrategy"]],"gino.strategies.GinoStrategy":[[35,4,1,"","create"],[35,5,1,"","engine_cls"],[35,5,1,"","name"]],"gino.transaction":[[36,2,1,"","GinoTransaction"]],"gino.transaction.GinoTransaction":[[36,4,1,"","commit"],[36,3,1,"","connection"],[36,4,1,"","raise_commit"],[36,4,1,"","raise_rollback"],[36,3,1,"","raw_transaction"],[36,4,1,"","rollback"]],gino:[[20,0,0,"-","aiocontextvars"],[21,0,0,"-","api"],[22,0,0,"-","bakery"],[19,1,1,"","create_engine"],[23,0,0,"-","crud"],[24,0,0,"-","declarative"],[25,0,0,"-","dialects"],[29,0,0,"-","engine"],[30,0,0,"-","exceptions"],[31,0,0,"-","ext"],[19,1,1,"","get_version"],[32,0,0,"-","json_support"],[33,0,0,"-","loader"],[34,0,0,"-","schema"],[35,0,0,"-","strategies"],[36,0,0,"-","transaction"]]},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","property","Python property"],"4":["py","method","Python method"],"5":["py","attribute","Python attribute"],"6":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:property","4":"py:method","5":"py:attribute","6":"py:exception"},terms:{"0":[0,2,4,10,11,12,17,23,26,27,28,39,43,44],"0020":43,"02":[12,17],"03":17,"04":[12,17],"05":17,"06":17,"07":17,"08":[12,17],"09":17,"0x10a8ba860":14,"1":[1,2,3,4,5,7,8,11,12,14,15,17,19,21,22,23,24,33,38,39,43,44,45],"10":[2,4,10,12,17,29,38,43,45],"100":[3,4,23,44],"101":[0,4],"106":41,"109":41,"11":[17,43,44],"112":41,"113":[10,41],"114":41,"11th":4,"12":[8,17,43,44],"123":7,"126":41,"127":44,"128":36,"13":[41,43,44],"14":[17,43],"141":41,"142":41,"146":41,"147":41,"15":[17,43],"159":41,"16":[11,17,43,44],"160":41,"17":[11,41,43,45],"174":41,"178":41,"18":[11,17,43],"180":41,"183":41,"184":41,"187":41,"19":[17,43],"191":41,"192":41,"193":41,"198":41,"1990":11,"2":[0,1,2,8,10,11,12,15,17,33,43,44,45],"20":[10,12,17,43,44],"2017":[17,43],"2018":[12,17],"2019":17,"202":41,"2020":[16,17],"204":41,"21":[17,43,44],"213":41,"215":41,"216":41,"22":[41,43],"224":41,"225":41,"228":41,"23":[12,17,43,44],"231":41,"24":[17,43],"249":3,"25":[17,43],"258":41,"26":[17,43],"261":41,"265":41,"27":[41,43],"275":41,"279":41,"28":[17,43],"280":41,"281":41,"282":41,"287":41,"288":41,"289":41,"29":[41,43],"291":41,"297":41,"298":41,"3":[1,2,3,8,10,12,14,15,17,20,31,43,44],"30":[41,43],"302":41,"304":41,"305":41,"307":41,"308":41,"309":41,"31":[41,43],"310":41,"313":41,"32":[23,36,41,43],"323":41,"32c0feba61ea":44,"32c0feba61ea_add_users_t":44,"33":[41,43],"333":41,"334":41,"335":41,"34":41,"351":41,"365":41,"378":41,"38":41,"381":41,"382":41,"387":41,"39":41,"393":41,"395":41,"396":41,"3apull_request":8,"4":[1,2,3,5,12,17,43,44],"40":7,"400":38,"401":41,"402":41,"403":41,"404":44,"406":41,"407":41,"408":41,"411":41,"42":23,"425":41,"427":41,"43":41,"431847":12,"433":41,"437":41,"440":41,"441":41,"447":41,"451":41,"457":41,"47":41,"478":41,"486":41,"487":41,"4888":43,"4c59ad":41,"5":[2,10,12,17,38,43,44],"504":41,"518":41,"520":41,"53010":44,"53015":44,"533":41,"538":41,"54":44,"5432":[8,38,39,44],"5433":8,"567":41,"569":41,"573":41,"577":41,"579":41,"582":41,"585":41,"59":41,"592":41,"599":41,"6":[1,2,8,10,17,23,43],"60":41,"600":[8,41],"609":41,"627":43,"628":41,"629":41,"63":41,"63562":44,"63563":44,"637":41,"638":41,"64":36,"655":41,"659":41,"660":41,"661":41,"662":41,"667":41,"67":41,"672":41,"673":41,"674":41,"685":41,"693":41,"694":41,"695":41,"696":41,"7":[1,2,6,8,10,12,17,20,23,43],"729":41,"73":41,"747":41,"75":41,"76":41,"79":41,"8":[2,8,10,17,39,43,44],"80":[41,44],"8000":44,"84":41,"87":[41,43],"89":41,"9":[4,8,41,43],"90":41,"\u00e1d\u00e1m":[10,41],"\u4e00\u4e2a\u6c47\u7387":43,"\u4e00\u5b9a\u8981\u5168":43,"\u4e00\u65e6\u4ee3\u7801\u521d\u5177\u89c4\u6a21":43,"\u4e00\u679a":43,"\u4e00\u6837":43,"\u4e00\u79d2\u53ef\u8bfb\u767e\u4e07\u884c\u7684":43,"\u4e00\u7ad9\u5f0f\u5730\u89e3\u51b3\u4e86\u5e38\u7528":43,"\u4e09\u5e74\u540e\u7684\u4eca\u5929":43,"\u4e0a\u4e0b\u6587\u7ba1\u7406":43,"\u4e0a\u624b\u540e\u4f9d\u7136\u53ef\u4ee5\u5feb\u901f":43,"\u4e0a\u7ed9":43,"\u4e0a\u9762\u90a3\u4e2a":43,"\u4e0d\u4f1a\u53bb\u65e0\u7aef\u731c\u6d4b\u4e3b\u4eba\u7684\u610f\u56fe":43,"\u4e0d\u540c\u7684\u662f":43,"\u4e0d\u65ad\u6f14\u8fdb\u6210\u719f":43,"\u4e0d\u662f":43,"\u4e0d\u662f\u4e00\u4e2a\u4f20\u7edf\u7684":43,"\u4e0d\u6b62\u8fd9\u6837":43,"\u4e0e":43,"\u4e13\u4e1a\u7248\u5168\u5bb6\u6876":43,"\u4e13\u4e1a\u9886\u57df\u7684":43,"\u4e1a\u4f59\u65f6\u95f4\u9664\u4e86\u5f00\u53d1\u7ef4\u62a4":43,"\u4e2d\u8d1f\u8d23\u6784\u5efa":43,"\u4e3a":43,"\u4e3a\u4e86\u8ba9\u4e0d\u540c\u5e94\u7528\u573a\u666f\u4e0b\u7684\u7528\u6237\u4f53\u9a8c\u5230\u6700\u5927\u7684\u5584\u610f":43,"\u4e3a\u4e86\u9e21\u6bdb\u849c\u76ae\u7684\u5c0f\u4e8b\u5927\u52a8\u5e72\u6208":43,"\u4e3a\u4ec0\u4e48\u8fd9\u4e48\u773c\u719f":43,"\u4e3b\u8981\u8bf4\u7684\u662f\u5f00\u53d1\u6548\u7387":43,"\u4e4b\u7985\u5b8c\u7f8e\u8868\u8fbe\u4e86":43,"\u4e5f\u53ef\u4ee5\u7528":43,"\u4e5f\u63d0\u4f9b\u4e86\u5bf9\u8c61\u6620\u5c04\u7684\u5de5\u5177":43,"\u4e5f\u662f":43,"\u4e5f\u66fe\u5728\u521b\u4e1a\u7684\u6f6e\u6d41\u4e2d\u7559\u4e0b\u8eab\u5f71":43,"\u4e5f\u6709\u4e00\u5957\u7cbe\u5999\u7684\u663e\u5f0f\u673a\u5236":43,"\u4e86\u89e3\u4e00\u4e0b":43,"\u4e8c\u5341\u4e94\u516d\u5e74\u7684\u7f16\u7a0b\u53f2\u548c\u5341\u4e09\u56db\u5e74\u7684\u5de5\u4f5c\u7ecf\u9a8c\u6559\u4f1a\u4e86\u6211\u8bb8\u591a\u8f6f\u4ef6\u5f00\u53d1\u7684\u5965\u4e49":43,"\u4e92\u91d1":43,"\u4eb2\u8eab\u7ecf\u5386\u5e76\u89c1\u8bc1\u4e86\u8f6f\u4ef6\u6280\u672f\u968f\u7740\u624b\u6e38":43,"\u4ec0\u4e48\u7684\u90fd\u6709":43,"\u4ece\u4e0a\u624b\u6559\u7a0b\u5230\u539f\u7406\u8bf4\u660e\u5e94\u6709\u5c3d\u6709":43,"\u4ece\u6bd4\u8f83\u65e9\u5c31\u89e3\u8026\u4e86\u4e0d\u540c":43,"\u4ece\u7b80\u5355\u793a\u8303\u5230\u751f\u4ea7\u73af\u5883\u7684\u5404\u79cd\u4f8b\u5b50\u54c1\u79cd\u9f50\u5168":43,"\u4ee5\u4e0b\u662f\u8fd1\u6765\u7edf\u8ba1\u5230\u7684\u5173\u4e8e":43,"\u4ee5\u53ca":43,"\u4ee5\u53ca\u4e0b\u9762\u8fd9\u4e9b\u4e00\u76f4\u9700\u8981\u7684\u5e2e\u52a9":43,"\u4ee5\u53ca\u4e2d\u6587":43,"\u4ee5\u540c\u65f6\u83b7\u53d6\u6240\u6709\u7684\u4e66\u548c\u4ed6\u4eec\u7684\u4f5c\u8005":43,"\u4ee5\u8282\u7701\u5b66\u4e60\u548c\u8fc1\u79fb\u6210\u672c":43,"\u4f18\u52bf\u4e0e\u4e0d\u8db3":42,"\u4f20\u7edf":43,"\u4f46":43,"\u4f46\u4e0d\u662f\u4e00\u4e2a\u4f20\u7edf\u7684":43,"\u4f46\u5e94\u7528\u5230\u5927\u578b\u9879\u76ee\u4e2d\u5374\u5341\u5206\u8003\u9a8c\u5f00\u53d1\u4eba\u5458\u7684\u5e73\u5747\u6c34\u5e73":43,"\u4f46\u662f\u5bf9\u4e8e\u66f4\u590d\u6742\u7684\u67e5\u8be2":43,"\u4f46\u6ca1\u5f81\u5f97\u540c\u610f\u5c31\u4e0d\u8d34\u51fa\u6765\u4e86":43,"\u4f46\u90fd\u662f\u540c\u884c\u5c31\u4e0d\u591a\u8bc4\u4ef7\u4e86":43,"\u4f60\u4e00\u5b9a\u4f1a\u6709\u611f\u77e5\u7684":43,"\u4f60\u53ef\u4ee5\u7528":43,"\u4f60\u751a\u81f3\u53ef\u4ee5\u624b\u5199\u4efb\u4f55":43,"\u4f60\u80af\u5b9a\u8981\u95ee\u4e00\u53e5":43,"\u4f8b\u5982\u524d\u9762\u7684":43,"\u4fc4\u6587\u7684\u7ffb\u8bd1":43,"\u4fc4\u8bed\u6559\u7a0b":43,"\u4fee":43,"\u501f\u9274\u4e86":43,"\u505a\u529f\u80fd":43,"\u5143":43,"\u5148\u8bf4":42,"\u5168\u90fd\u517c\u5bb9":43,"\u5173\u4e8e\u4f5c\u8005":42,"\u5176\u4e2d":43,"\u518d\u52a0\u4e0a":43,"\u518d\u8bf4":42,"\u5199":43,"\u51fa\u54c1\u5fc5\u5c5e\u7cbe\u54c1\u7684":43,"\u51fa\u54c1\u65b9\u662f":43,"\u51fa\u95ee\u9898\u627e\u4e0d\u5230\u539f\u56e0":43,"\u5219\u4f1a\u5c06\u8fd9\u4e9b\u53d8\u66f4\u5e94\u7528\u5230\u6570\u636e\u5e93\u91cc":43,"\u5219\u662f":43,"\u521b\u9020\u51fa\u4e86\u4e00\u79cd\u7206\u70b8\u5f0f\u7684\u5316\u5b66\u53cd\u5e94":43,"\u52a0\u4e00\u9897\u661f\u661f":43,"\u52a0\u8f7d\u5668\u4e0e\u5173\u7cfb":43,"\u52a0\u8f7d\u5668\u7684\u7528\u6cd5\u4e5f\u662f\u5f88\u7b80\u5355\u7684":43,"\u5341\u5206\u5feb\u6377":43,"\u5343\u661f\u9879\u76ee":43,"\u5373\u5173\u7cfb\u5bf9\u8c61\u6620\u5c04":43,"\u5373\u88c5\u5373\u7528":43,"\u539f\u6559\u65e8\u4e3b\u4e49\u8005":43,"\u53bb":43,"\u53c2\u4e0e\u8ba8\u8bba":43,"\u53c2\u8003\u6587\u732e":42,"\u53c8\u7b80\u5355\u660e\u4e86\u5730\u8bbf\u95ee\u6570\u636e\u5e93\u5462":43,"\u53cd\u566c\u7684\u60c5\u666f":43,"\u53e6\u4e00\u65b9\u9762":43,"\u53e6\u5916":43,"\u53ea\u613f\u5b9a\u4e49":43,"\u53ea\u6709\u5f02\u6b65\u6267\u884c\u65f6\u624d\u7528\u5230":43,"\u53ef\u4ee5\u76f4\u63a5\u83b7\u53d6\u5230":43,"\u53ef\u4ee5\u8c28\u614e\u5730\u7528\u4e8e\u751f\u4ea7\u73af\u5883\u4e86":43,"\u53ef\u5b9a\u5236\u5316\u7684\u52a0\u8f7d\u5668":43,"\u53ef\u9009":43,"\u5404\u4e2a":43,"\u5404\u5927\u6d41\u884c\u5f02\u6b65":43,"\u5404\u79cd":43,"\u540c\u65f6":43,"\u540c\u65f6\u4e5f\u4e0d\u9700\u8981\u4e3a\u8fd9\u79cd\u660e\u786e\u6027\u4ed8\u51fa\u8fc7\u5927\u7684\u5de5\u7a0b\u4ee3\u4ef7":43,"\u5462":43,"\u548c":43,"\u54a6":43,"\u54e6\u4e0d":43,"\u56de\u7b54\u95ee\u9898":43,"\u56e0\u4e3a\u7269\u6781\u5fc5\u53cd":43,"\u56e0\u6b64":43,"\u56e0\u6b64\u8fd8\u4e0d\u80fd\u5b8c\u5168\u53d1\u6325":43,"\u5728\u5e26\u6765\u751f\u6d3b\u4fbf\u5229\u7684\u540c\u65f6":43,"\u5728\u6267\u884c\u6548\u7387\u4e0a\u4e5f\u6ca1\u843d\u4e0b":43,"\u5728\u7ebf\u6e38\u620f\u7b49\u9ad8\u5e76\u53d1\u9886\u57df":43,"\u5728\u8fd9\u4e2a\u70e7\u8111\u7684\u5f02\u6b65\u4e16\u754c\u91cc":43,"\u5728\u8fd9\u91cc\u5c31\u4e0d\u4e00\u4e00\u5217\u4e3e\u4e86":43,"\u57fa\u4e8e":43,"\u57fa\u7840\u6559\u7a0b":43,"\u586b\u8865\u4e86\u56fd\u5185\u5916":43,"\u589e\u5220\u6539\u67e5":43,"\u5927":43,"\u5927\u5b66\u91cc\u5f00\u59cb\u5199":43,"\u5927\u91cf\u7684\u6210\u529f\u6848\u4f8b\u4e5f\u8bc1\u660e\u4e86":43,"\u5929\u751f\u538c\u6076":43,"\u5982\u679c\u50cf\u8fd9\u6837":43,"\u5988\u5988\u518d\u4e5f\u4e0d\u7528\u62c5\u5fc3\u6211\u4e0d\u4f1a\u96c6\u6210":43,"\u5b83\u4eec":43,"\u5b83\u4eec\u5173\u6ce8\u7684\u91cd\u70b9\u4e0e":43,"\u5b83\u7684\u5168\u79f0\u662f":43,"\u5b83\u7684\u5b9e\u4f8b":43,"\u5b98\u5ba3":42,"\u5b9a\u4e0b\u4e86\u4e24\u4e2a\u4e1a\u7ee9\u76ee\u6807":43,"\u5b9e\u4f8b":43,"\u5b9e\u4f8b\u7684":43,"\u5b9e\u4f8b\u8bbe\u7f6e\u5230":43,"\u5bf9":43,"\u5bf9\u4e8e\u4e0a\u4e86\u89c4\u6a21\u7684\u5f02\u6b65\u5de5\u7a0b\u9879\u76ee\u6765\u8bf4\u5c24\u4e3a\u91cd\u8981":43,"\u5bf9\u4e8e\u5982\u4f55\u5c06\u6570\u636e\u5e93\u67e5\u8be2\u7ed3\u679c\u7ec4\u88c5\u6210\u5185\u5b58\u5bf9\u8c61\u53ca\u5176\u5c5e\u6027":43,"\u5bf9\u4e8e\u7b80\u5355\u76f4\u89c2\u7684\u4e00\u5bf9\u4e00\u52a0\u8f7d":43,"\u5bf9\u5176\u6267\u884c":43,"\u5bf9\u8c61":43,"\u5bf9\u8c61\u5173\u7cfb\u6620\u5c04":43,"\u5c06\u6570\u636e\u5e93\u8fd4\u56de\u7ed3\u679c\u7684\u6bcf\u4e00\u884c\u4e2d":43,"\u5c0f\u65f6\u5019\u5199":43,"\u5c31\u53d8\u6210\u4e86\u4eba\u540d":43,"\u5c31\u5b9e\u73b0\u4e86":43,"\u5c31\u662f\u4ed6\u4eec\u7684\u4f5c\u54c1":43,"\u5c31\u662f\u5185\u5b58\u91cc\u9762\u7684\u5e38\u89c4\u5bf9\u8c61":43,"\u5c31\u662f\u660e\u786e\u6027\u7684\u5173\u952e":43,"\u5c31\u662f\u6709\u4eba\u53d7\u4e0d\u4e86\u4e86\u81ea\u5df1\u5199\u4e86\u4e00\u4e2a\u7c7b\u578b\u6ce8\u89e3":43,"\u5c31\u6ca1\u6709\u6570\u636e\u5e93\u64cd\u4f5c":43,"\u5c5e\u4e8e":43,"\u5c5e\u6027\u4e0a":43,"\u5c81\u5f00\u59cb\u63a5\u89e6\u7f16\u7a0b":43,"\u5de5\u4f5c\u5934\u4e94\u5e74\u8f6c\u5411\u4e86":43,"\u5df2\u7ecf\u521d\u6b65\u5177\u5907\u53d1\u5e03":43,"\u5e74\u521b\u4f5c\u4e4b\u521d":43,"\u5e74\u751f\u4eba":43,"\u5e76\u4e0d\u662f\u4ece\u5934\u9020\u8f6e\u5b50":43,"\u5e76\u63a5\u89e6\u5230\u4e86":43,"\u5efa\u8bbe\u793e\u4f1a\u4e3b\u4e49":42,"\u5f00\u53d1\u4eba\u5458\u7684\u65f6\u95f4\u786e\u5b9e\u6bd4\u673a\u5668\u7684\u65f6\u95f4\u503c\u94b1":43,"\u5f00\u53d1\u5e94\u7528\u7a0b\u5e8f\u7684\u65f6\u5019\u4e0d\u7528\u62c5\u5fc3\u4f1a\u88ab\u610f\u6599\u4e4b\u5916\u7684\u884c\u4e3a\u6240\u60ca\u5413\u5230":43,"\u5f00\u6e90\u793e\u533a\u91cc\u4e5f\u76f8\u7ee7\u51fa\u73b0\u4e86\u50cf":43,"\u5f00\u7bb1\u5373\u7528\u7684\u6570\u636e\u5e93\u53d8\u66f4\u7ba1\u7406\u5de5\u5177":43,"\u5f02\u6b65":43,"\u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668":42,"\u5f02\u6b65\u7f16\u7a0b\u8fd9\u4e2a\u8bdd\u9898\u4e5f\u5728\u9010\u6e10\u5347\u6e29":43,"\u5f15\u64ce":43,"\u5f15\u64ce\u4e0e\u8fde\u63a5":43,"\u5f62\u4f3c\u800c\u795e\u4e0d\u4f3c":43,"\u5f80\u5f80\u4f1a\u9009\u62e9\u727a\u7272\u660e\u786e\u6027":43,"\u5f88\u7b80\u5355\u7684\u4e00\u4e2a\u5916\u8fde\u63a5\u67e5\u8be2":43,"\u5f97\u5929\u72ec\u539a\u7684\u7075\u6d3b\u6027":43,"\u5feb\u4e50\u5730\u7f16\u7a0b":43,"\u5feb\u6377\u65b9\u5f0f":43,"\u6027\u80fd\u83ab\u540d\u5176\u5999\u7684\u5dee":43,"\u610f\u5473\u7740\u6211\u4eec\u8981\u8df3\u51fa\u53bb\u6267\u884c\u6570\u636e\u5e93\u64cd\u4f5c\u4e86":43,"\u6210\u529f\u5b9e\u73b0\u4e86\u5bf9\u540c\u671f\u7ade\u54c1":43,"\u6211\u5c31\u7ed9":43,"\u6211\u771f":43,"\u6211\u7d22\u6027\u5728":43,"\u6216\u8005\u7528":43,"\u6240\u4ee5":43,"\u6240\u4ee5\u5728":43,"\u6240\u4ee5\u6b22\u8fce\u5927\u5bb6\u4e00\u8d77\u6765\u5efa\u8bbe":43,"\u6240\u4ee5\u8fd9\u4e9b\u529f\u80fd\u4f1a\u9646\u7eed\u5728":43,"\u6240\u6709\u4eba":43,"\u6240\u6709\u8fd9\u4e9b\u95ee\u9898\u5982\u679c\u518d\u653e\u8fdb\u5f02\u6b65\u7f16\u7a0b\u7684\u73af\u5883\u91cc":43,"\u624b\u6cd5":43,"\u6267\u884c":43,"\u627e":43,"\u6307\u54ea\u513f\u6253\u54ea\u513f":43,"\u6362\u53e5\u8bdd\u8bf4":43,"\u63d0":43,"\u63d0\u5347\u4e86\u5199\u4ee3\u7801\u7684\u5e78\u798f\u6307\u6570":43,"\u63d0\u5efa\u8bae":43,"\u6570\u636e\u5e93\u4e8b\u52a1":43,"\u6570\u636e\u5e93\u4e8b\u52a1\u5c01\u88c5\u548c\u5d4c\u5957":43,"\u6587\u5a31":43,"\u65b0\u5a92\u4f53":43,"\u65b9\u4fbf\u5feb\u6377":42,"\u65b9\u8a00\u548c\u9a71\u52a8\u7684\u96c6\u6210":43,"\u65e0\u989d\u5916\u4f9d\u8d56\u5173\u7cfb":43,"\u65e2\u7b80\u5355\u53c8\u660e\u4e86\u6709\u6ca1\u6709":43,"\u660e\u786e\u6027":43,"\u662f":43,"\u662f\u4e00\u4e2a":43,"\u662f\u4e00\u4e2a\u5f00\u6e90\u9879\u76ee":43,"\u662f\u4e00\u7c7b\u5f00\u53d1\u4eba\u5458\u559c\u95fb\u4e50\u89c1\u7684\u6548\u7387\u5de5\u5177":43,"\u662f\u4e0d\u662f\u5341\u5206\u65b9\u4fbf":43,"\u662f\u5b8c\u5168\u65e0\u72b6\u6001\u7684\u666e\u901a":43,"\u662f\u7528\u6765\u8bbf\u95ee\u6570\u636e\u5e93\u7684":43,"\u662f\u7684":43,"\u662f\u8c01":42,"\u66f4\u591a\u7684\u4f8b\u5b50\u548c\u6587\u6863":43,"\u66f4\u91cd\u8981\u7684\u662f\u5e26\u6765\u4e86\u6574\u4e2a":43,"\u6700\u540e":43,"\u6700\u540e\u4e5f\u662f\u6700\u91cd\u8981\u7684":43,"\u6700\u540e\u5c06":43,"\u6700\u5927\u7a0b\u5ea6\u7684\u4fbf\u5229":43,"\u6700\u5c11\u4fb5\u5165\u578b":43,"\u6709\u6ca1\u6709\u529e\u6cd5\u53ef\u4ee5\u65e2\u65b9\u4fbf\u5feb\u6377":43,"\u670d\u52a1":43,"\u671f\u95f4\u8d21\u732e\u4e86":43,"\u6765\u4fee\u6539\u5c5e\u6027":43,"\u6765\u521b\u5efa\u65b0\u7684\u5b9e\u4f8b":43,"\u6765\u6362\u53d6\u4fbf\u6377\u6027":43,"\u6781\u5927\u5730":43,"\u67d0\u4e9b\u573a\u666f\u4e0b":43,"\u6846\u67b6":43,"\u6846\u67b6\u4e86":43,"\u6846\u67b6\u63d2\u4ef6\u7684\u7ef4\u62a4\u5de5\u4f5c\u9700\u8981\u591a\u4eba\u8ba4\u9886":43,"\u6846\u67b6\u6765\u8bf4\u662f\u4e0d\u53ef\u63a5\u53d7\u7684":43,"\u6846\u67b6\u7684\u5b9a\u5236\u7248\u63d2\u4ef6":43,"\u6b22\u8fce\u6765\u5230":43,"\u6b63\u72b9\u5982":43,"\u6bd4\u5982\u4e00\u4e2a\u7528\u6237\u53ef\u80fd\u5199\u4e86\u5f88\u591a\u672c\u4e66":43,"\u6bd4\u5982\u6267\u884c":43,"\u6bd4\u5982\u6ca1\u6709\u7167\u987e\u5230":43,"\u6bd4\u5982\u7528":43,"\u6bd4\u5982\u8bf4":43,"\u6c7d\u8f66\u7b49\u884c\u4e1a\u7684\u8d77\u8d77\u4f0f\u4f0f":43,"\u6ca1\u6709":43,"\u6ca1\u6709\u53d1\u7968":43,"\u6ca1\u6709\u9519":43,"\u6df1\u53d7\u4fc4\u7f57\u65af\u548c\u4e4c\u514b\u5170\u4eba\u6c11\u7684\u7231\u6234":43,"\u706b\u529b\u5168\u5f00\u578b":43,"\u7136\u540e\u5b9a\u5236\u52a0\u8f7d\u5668\u81ea\u52a8\u52a0\u8f7d\u6210\u671f\u671b\u7684\u5bf9\u8c61\u5173\u7cfb":43,"\u7136\u540e\u5c06\u8be5\u884c\u4e2d\u5269\u4e0b\u7684\u5c5e\u4e8e":43,"\u7136\u540e\u8fd9\u6837\u6765\u52a0\u8f7d\u8fd9\u79cd\u591a\u5bf9\u4e00\u5173\u7cfb":43,"\u7248\u672c\u4e2d\u8ddf\u4e0a":43,"\u7279\u522b\u611f\u8c22":43,"\u751f\u957f\u7684\u6e29\u5e8a":43,"\u7528":43,"\u7528\u6237\u5305":43,"\u7535\u5546":43,"\u7684":43,"\u7684\u540c\u5b66\u6765\u8bf4\u53ef\u80fd\u5e76\u4e0d\u964c\u751f":43,"\u7684\u57fa\u7840\u4e0a\u5f00\u53d1\u7684":43,"\u7684\u589e\u5f3a\u63d2\u4ef6":43,"\u7684\u590d\u6742\u5ea6\u4e86":43,"\u7684\u5b57\u6bb5\u52a0\u8f7d\u6210\u4e00\u4e2a":43,"\u7684\u5b66\u4e60\u66f2\u7ebf\u524d\u5e73\u540e\u9661":43,"\u7684\u5b9a\u4e49\u98ce\u683c":43,"\u7684\u5e94\u7528\u6848\u4f8b":43,"\u7684\u5e95\u5c42\u6838\u5fc3":43,"\u7684\u5f3a\u529b\u52a0\u6301":43,"\u7684\u5f88\u591a\u8bbe\u8ba1\u90fd\u53d7\u5230\u4e86\u660e\u786e\u6027\u7684\u5f71\u54cd":43,"\u7684\u652f\u6301":43,"\u7684\u6587\u6863":43,"\u7684\u6700\u5927\u4f18\u52bf\u8fd8\u662f\u5728\u4e8e\u5145\u5206\u5e73\u8861\u4e86\u5f00\u53d1\u6548\u7387\u548c\u660e\u786e\u6027\u4e4b\u95f4\u7684\u8fa9\u8bc1\u77db\u76fe\u5173\u7cfb":43,"\u7684\u6f5c\u80fd":43,"\u7684\u751f\u6001\u73af\u5883":43,"\u7684\u7528\u6237\u5bf9\u8c61":43,"\u7684\u7acb\u573a":43,"\u7684\u7c7b\u578b\u63d0\u793a":43,"\u7684\u8d21\u732e":43,"\u7684\u9012\u5f52\u5b9a\u4e49":43,"\u7684\u964d\u7ef4\u6253\u51fb":43,"\u76ee\u524d\u4e5f\u662f\u4e0d\u652f\u6301\u7684":43,"\u76ee\u524d\u5728\u829d\u5927\u7ee7\u7eed\u505a\u751f\u7269\u5927\u6570\u636e\u76f8\u5173\u7684\u5f00\u6e90\u9879\u76ee":43,"\u76ee\u524d\u6025\u9700\u5e2e\u52a9\u7684\u6709":43,"\u76ee\u524d\u652f\u6301\u4e09\u79cd\u4e0d\u540c\u7a0b\u5ea6\u7684\u7528\u6cd5":43,"\u76ee\u524d\u7684\u4e0d\u8db3\u4e4b\u5904\u8fd8\u6709\u4e00\u4e9b":43,"\u77ff\u5708":43,"\u793e\u4ea4":43,"\u7a0d\u6709\u4e0d\u540c":43,"\u7a33\u5b9a\u7248\u53d1\u5e03\u7684\u524d\u5915\u505a\u4e2a\u5e74\u7ec8\u603b\u7ed3":43,"\u7a33\u5b9a\u7248\u7684\u5404\u79cd\u6761\u4ef6":43,"\u7a7a\u624b\u63a5":43,"\u7b14\u8005\u5de5\u4f5c\u4e0a\u5f00\u53d1\u7684\u4e00\u4e2a\u670d\u52a1":43,"\u7b49":43,"\u7b49\u4f18\u79c0\u7684\u5f02\u6b65":43,"\u7b49\u5230\u9700\u8981\u64cd\u4f5c\u6570\u636e\u5e93\u7684\u65f6\u5019":43,"\u7b49\u591a\u9879\u4fbf\u6377\u529f\u80fd":43,"\u7b49\u5f00\u6e90\u9879\u76ee\u4e14\u4e00\u53d1\u4e0d\u53ef\u6536\u62fe":43,"\u7b49\u6846\u67b6\u7684\u9646\u7eed\u6d8c\u73b0":43,"\u7b49\u9879\u76ee\u4e86\u89e3\u4e86\u5f02\u6b65\u7f16\u7a0b":43,"\u7b80\u5355\u660e\u4e86":42,"\u7c7b":43,"\u7c7b\u4f3c":43,"\u7cbe\u51c6\u63a7\u5236\u52a0\u8f7d\u884c\u4e3a":43,"\u7ec8\u8eab\u4e0d\u5a5a\u578b":43,"\u7edd\u4e0d\u662f\u9ed1\u54c8":43,"\u7edd\u5bf9\u7eff\u8272\u73af\u4fdd\u65e0\u6bd2\u526f\u4f5c\u7528":43,"\u7ef4\u57fa\u767e\u79d1":43,"\u7ef4\u62a4\u793e\u533a":43,"\u800c":43,"\u800c\u5168\u6743\u4ea4\u7ed9\u7528\u6237\u6765\u660e\u786e\u5730\u5b9a\u4e49":43,"\u800c\u662f\u5728":43,"\u804a\u5929\u673a\u5668\u4eba":43,"\u80fd\u53eb\u4e0a\u540d\u5b57\u7684\u50cf":43,"\u80fd\u5728\u5feb\u901f\u539f\u578b\u5f00\u53d1\u4e2d\u5927\u5c55\u8eab\u624b":43,"\u81ea\u7136\u662f\u4f3a\u5019\u5230\u5bb6\u7684":43,"\u867d\u7136\u6587\u6863\u8fd8\u5728\u52aa\u529b\u7f16\u5199\u4e2d":43,"\u867d\u7136\u662f":43,"\u8868":43,"\u8868\u7ed3\u6784\u5b9a\u4e49":43,"\u88ab\u5e7f\u6cdb\u5e94\u7528\u4e8e\u8bf8\u5982\u5b9e\u65f6\u6c47\u7387":43,"\u8981\u7528":43,"\u8bbf\u95ee\u5c5e\u6027":43,"\u8dd1\u8d77\u6765\u4e5f\u662f\u53ef\u4ee5\u98de\u5feb\u7684":43,"\u8f7b\u91cf\u7ea7":43,"\u8fc1\u79fb":43,"\u8fd8\u4f1a\u5076\u5c14\u4fee\u4e00\u4fee":43,"\u8fd8\u4f1a\u8fd4\u56de\u4e00\u4e2a\u5305\u542b\u672c\u6b21\u53d8\u66f4\u7684\u4e2d\u95f4\u7ed3\u679c":43,"\u8fd8\u63d0\u4f9b\u4e86":43,"\u8fd8\u662f\u7b14\u8005\u81ea\u5df1\u5199\u7684\u4e00\u4e2a\u5de5\u5177":43,"\u8fd8\u6709\u51e0\u4e2a\u5546\u7528\u7684":43,"\u8fd8\u6709\u5f88\u591a\u7c7b\u4f3c\u7684\u7279\u6027":43,"\u8fd8\u8d34\u5fc3\u5730\u63d0\u4f9b\u4e86\u4e2d\u6587\u6587\u6863":43,"\u8fd9\u4e2a\u9879\u76ee\u5c31\u53eb":43,"\u8fd9\u4e48\u505a\u9664\u4e86\u80fd\u4fdd\u6301\u719f\u6089\u7684\u5473\u9053":43,"\u8fd9\u4e48\u5b9a\u4e49\u8868\u7ed3\u6784\u751a\u81f3\u8ba9\u4eba\u6709\u70b9\u5c0f\u5174\u594b":43,"\u8fd9\u4e9b\u64cd\u4f5c\u90fd\u4e0d\u4f1a\u8bbf\u95ee\u6570\u636e\u5e93":43,"\u8fd9\u4ee3\u7801\u4f60\u8ba9\u6211\u600e\u4e48\u8c03\u8bd5":43,"\u8fd9\u5bf9\u4e8e\u4e00\u6b3e\u4f18\u79c0\u7684\u5f02\u6b65":43,"\u8fd9\u5c31\u662f":43,"\u8fd9\u662f\u8c01":43,"\u8fd9\u91cc\u7684":43,"\u8fde\u63a5\u6c60\u7ba1\u7406\u548c\u61d2\u52a0\u8f7d":43,"\u901a\u8fc7":43,"\u90a3\u4e3a\u4ec0\u4e48\u975e\u8bf4":43,"\u90a3\u5c31\u662f":43,"\u90e8":43,"\u90fd\u662f\u53ea\u5728\u5185\u5b58\u91cc\u4fee\u6539\u5bf9\u8c61\u7684\u5c5e\u6027":43,"\u90fd\u6709\u53ef\u80fd\u89e6\u53d1\u4e00\u5927\u5806\u610f\u60f3\u4e0d\u5230\u7684\u6570\u636e\u5e93\u8c03\u7528":43,"\u914d\u5408\u4e00\u4e2a\u76f4\u89c2\u7684\u52a0\u8f7d\u5668":43,"\u91cc\u7684\u5bf9\u8c61\u6620\u5c04\u4e0d\u80fd\u4e22":43,"\u91cd\u89c6\u5f00\u53d1\u6548\u7387\u7684\u6982\u5ff5\u5bf9\u4e8e\u5199":43,"\u957f\u671f\u6d3b\u8dc3\u7684\u8d21\u732e\u8005\u8fd8\u80fd\u83b7\u8d60\u4ef7\u503c":43,"\u968f\u4fbf\u4e00\u53e5":43,"\u968f\u7740":43,"\u968f\u7740\u8fd9\u51e0\u5e74":43,"\u975e\u5178\u578b\u5f02\u6b65":43,"\u9879\u76ee\u5916":43,"\u9879\u76ee\u6216\u591a\u6216\u5c11\u90fd\u4f1a\u9047\u5230":43,"\u9879\u76ee\u7684\u8d21\u732e":43,"\u9886\u57df\u7684\u7a7a\u767d":43,"\u9ad8\u6027\u80fd\u6a21\u677f\u9879\u76ee":43,"abstract":[4,24,33,41],"boolean":[11,29,33],"break":[1,3,15,41,44],"case":[2,3,4,6,10,12,13,14,15,21,26,41],"class":[2,6,7,10,11,12,13,14,21,22,23,24,26,27,28,29,32,33,34,35,36,38,41,43,44,45],"default":[2,3,4,6,7,8,11,12,13,14,15,19,21,23,24,26,27,29,32,33,38,39,41,44,45],"do":[1,2,3,4,5,6,7,8,12,15,21,29,38,39,41,45],"enum":[26,27,41],"export":[6,8],"final":[1,2,4,10,29,41,44,45],"float":[11,26],"function":[2,8,11,20,22,26,27,29,33,44],"ila\u00ef":41,"import":[1,2,3,4,6,7,10,11,12,13,14,20,21,24,31,33,35,38,39,41,43,44,45],"int":[11,15,38,44],"long":[1,2,3,4,15,29,38,39],"micha\u0142":41,"new":[1,2,3,4,6,7,8,10,12,14,21,22,23,24,26,29,33,35,38,41,42,45],"null":[2,41],"public":14,"ram\u00edrez":43,"return":[1,2,3,4,7,10,11,12,14,15,21,22,23,24,26,27,29,33,35,38,39,41,44,45],"sebasti\u00e1n":43,"short":[2,3,4,7,14],"static":[28,44],"super":[4,12],"switch":[1,2,3,21,41],"transient":2,"true":[2,3,4,6,7,10,11,12,14,15,21,23,24,26,27,28,29,33,34,38,39,41,43,44,45],"try":[1,2,3,4,10,15,29,36,41,44,45],"var":8,"while":[1,2,4,10,12,14,21,23,29,36,41,45],"za\u0165ko":41,A:[1,2,7,9,12,15,21,22,23,24,29,33,35,38,39,40,41,43,45],AS:[3,11,12,45],AT:12,And:[1,2,3,6,7,8,11,12,23,41,44,45],As:[1,2,3,4,7,10,12,14,15,38,44,45],At:[2,3,12,21,44,45],Be:[0,10,44],Being:4,But:[1,2,3,4,12,44],By:[2,3,7,8,12,23,24,29,39,45],For:[1,2,3,4,8,10,12,14,21,23,29,33,36,41,44,45],IN:[3,12],INTO:[15,45],IS:[9,38,40],If:[1,2,3,4,6,7,8,10,11,12,14,20,21,22,23,24,26,27,29,33,35,39,41,44,45],In:[1,2,3,4,6,7,10,12,14,21,29,36,44,45],Is:[1,5,16,43,45],It:[1,2,3,4,6,8,10,12,14,15,29,44,45],NOT:[12,24],No:[1,3,6,19,44],Not:[10,16,24,33,43,45],ON:[11,12,23,43],On:[1,2,23],One:[1,5,6,29],Or:[2,3,7,10,14,38,39,41,44,45],That:[1,2,3,4,6,12,15,38,44,45],The:[0,2,4,7,8,10,11,12,13,14,15,21,22,23,24,26,27,29,33,36,38,39,41,43,44,45],Their:24,Then:[2,3,4,7,8,12,14,29,44,45],There:[1,2,3,4,13,14,29,41,45],These:[4,7,41],To:[1,2,3,6,7,8,10,12,13,21,38,39,41,44,45],WITH:8,Will:44,With:[1,2,3,4,12,23,44,45],_:22,__all__:41,__attr_factory__:24,__init__:[12,44],__main__:38,__metadata__:24,__model__:41,__name__:[24,38,44],__repr__:38,__table__:[14,23,24],__table_args__:[24,41,45],__tablename__:[6,7,10,11,12,14,24,38,41,43,44,45],__values__:24,_base:27,_bind:10,_child:12,_children:12,_engin:[26,27],_event:[26,27],_floattyp:26,_idx1:45,_idx2:45,_integertyp:26,_is_metadata_oper:34,_lastrowid:26,_matchtyp:26,_name_idx:10,_numerictyp:26,_parent:12,_pk:45,_sa:26,_schematranslatemap:29,_test:44,_update_request_cl:41,abandon:41,abcd:8,abil:[12,41],abl:[3,4,44],abnormal_detect:11,abort:38,about:[1,2,4,6,8,10,14,15,22,23,29,41,45],abov:[1,2,3,4,7,10,12,33,44,45],absolut:[2,4],accept:[2,3,7,21,23,26,27,29,41,45],access:[0,2,3,10,14,15,24,29,36,38,41,44,45],access_log:11,accessor:26,accid:2,accord:[3,4,29,33,39],achiev:[1,10,12,14,21,29],acid:3,acquir:[2,3,4,14,21,26,27,28,29,36,38,41],acquisit:4,across:[7,33,44],act:[1,2,45],action:[8,36],activ:[7,8,29,44],actual:[1,2,3,4,7,12,14,15,23,24,29,38,44,45],ad:[3,7,19,21,24,29,41,44,45],adapt:[3,45],add:[1,2,3,4,6,8,10,12,21,22,23,31,41,42,45],add_child:12,add_us:44,addit:[1,2,3,7,11,12,33],addition:[4,23],address:[6,14],adjac:10,adjust:44,admin:5,ado:7,adopt:41,advanc:5,advic:4,affect:[3,7,22,23,41,45],after:[1,2,3,4,6,7,14,15,21,23,24,26,27,29,33,38,41,44,45],after_get:11,afterward:[26,27],ag:[11,23,36],again:[1,2,3,4,10,12,29,44,45],against:8,age_idx:11,aggreg:23,aintq:43,aiocontextvar:[2,5,17,18,19,41,43],aiohttp:[41,43],aiomysql:[17,18,19,25],aiomysqldbapi:26,aiomysqldialect:26,aiomysqlexecutioncontext:26,aiomysqliter:26,alemb:[5,11,14,21,42,43,45],alembic_sampl:6,ali:10,alia:[10,12,21,22,23,26,27,28,29,33,35,41],aliasload:33,alik:41,all:[1,2,3,4,6,7,8,10,11,12,14,15,21,23,24,26,28,29,33,36,38,39,41,44,45],all_us:45,allow:[1,2,3,14,21,24,26,27,41,44,45],alon:2,alpha:[10,41],alpin:[8,44],alreadi:[1,3,4,12,24,45],alright:1,also:[1,2,3,4,6,7,10,12,14,15,21,22,23,24,26,27,29,31,33,41,44,45],altern:[2,10,13,29,44,45],although:[3,12],alwai:[2,3,6,8,10,14,21,23,29,33,36,38,41,44,45],amaz:[10,45],among:3,amount:2,an:[1,2,3,4,7,8,10,12,13,14,15,21,22,23,24,26,27,29,31,33,36,38,41,43,44,45],analysi:44,andrei:43,ani:[1,2,3,4,6,8,10,21,24,26,27,29,31,33,41],anoth:[1,2,4,12,22,23,29,33,45],answer:[1,3,10],anyth:[4,8,14,29,41],anywai:3,api:[0,2,4,5,10,14,17,19,22,26,27,29,36,42,43,45],apirout:44,apk:44,app:[4,10,13,38,39,41,44],appear:[1,3],append:23,append_where_primary_kei:23,appli:[2,3,5,14,23,29,36,43,44,45],applic:[4,7,10,21,39,41,44,45],appreci:8,approach:[1,3,4,7,10],ar:[1,2,3,4,5,8,10,11,12,13,14,15,21,23,24,29,33,36,38,41,44,45],arbitrari:4,archlinux:43,arg:[19,21,23,24,26,27,28,29,34,36],argument:[2,3,7,10,12,19,21,22,23,26,27,29,33,35,39,41,45],around:[3,29,43],arq:43,arrai:[11,27,41],arrayproperti:[11,32],arriv:1,arrrrh:1,articl:[3,8],artifact:44,asap:1,ascend:12,ascii_lett:[10,12],asgi:44,ask:[2,5],assembl:[2,12],assert:[15,24,36,41,44,45],assertionerror:41,assign:[2,26],assist:12,associ:[2,14],assum:[1,3,4,7,44],assumpt:[3,36],async:[0,1,2,4,7,10,12,14,15,21,23,26,27,28,29,34,35,36,38,41,43,44,45],async_execut:[26,27,28],async_main:3,asyncdialectmixin:[26,27,28],asyncenum:[26,27],asynchron:[0,2,3,12,14,15,16,21,29,36,45],asyncio:[0,1,2,3,5,12,14,16,20,43,45],asyncpg:[2,3,4,10,13,14,15,16,17,18,19,25,29,35,39,41,43,44,45],asyncpg_deleg:41,asyncpgcompil:27,asyncpgcursor:27,asyncpgdbapi:27,asyncpgdialect:[3,27],asyncpgexecutioncontext:27,asyncpgiter:27,asyncpgjsonpathtyp:27,asyncpgsa:[14,43],asyncschemadropp:34,asyncschemagener:34,asyncschematypemixin:34,asyncvisitor:34,ath:23,atom:23,attack:44,attent:38,attribut:[3,10,12,14,23,24,29,33,41,45],attributeerror:21,audienc:45,audit_profil:11,aur:43,austin:10,auth_plugin:26,authent:4,author:[4,21,43,44],author_id:43,auto:[0,11,23,24,44],autocommit:[0,4,26],autogener:[6,44],autom:[12,44],automat:[3,7,12,20,22,23,24,29,33,35,36,38,44],avail:[2,5,10,14,15,23,24,29,36,39,41,44],averchenkov:41,avoid:[3,4,33],awai:[2,4],await:[1,2,3,4,7,10,11,12,14,15,21,23,29,33,36,38,39,41,43,44,45],await_onli:3,awar:44,awesom:[1,43],b:[1,8,44],back:[1,2,3,4,12,15,21,29,36,41],backend:44,background:[3,16],backport:[2,10,41,44],backward:41,bad:[3,41],bake:[5,19,21,22,41],baked_queri:[26,27,28],bakedqueri:[5,22],bakeri:[5,17,18,19,21,26,27],balanc:[4,23],bar:1,barancsuk:41,bare:[1,4,5],base:[3,6,10,12,13,17,18,19,21,22,23,24,25,26,27,29,30,32,33,34,35,36,44],base_exp:32,basedbapi:[26,27,28],baseexcept:[15,36],basemodel:44,basic:[2,3,4,5,23,42,43],batch:[5,23,45],bayer:[4,43],becaus:[1,2,3,4,7,10,14,15,21,23,36,41,44,45],becom:[2,3,14],been:[1,2,26,27,41],befor:[1,2,3,4,7,8,10,12,14,15,22,23,26,29,36,38,44,45],before_set:11,begin:[1,3,4,7,26,27,28],beginn:45,begun:3,behav:[23,41],behavior:[2,3,10,15,23,29,38,41],behind:[2,3,4,7,12,23,41,44],being:[1,2,3,4,14,23,41],belong:15,below:[8,10,11,44],benefici:4,besid:10,best:[1,8,41,44],beta:41,better:[4,41,43],between:[1,2,4,41],beyond:4,biginteg:[21,38,44],bin:44,binari:[28,41],bind:[2,4,7,10,14,15,21,22,23,26,27,34,41,45],bind_processor:[2,27],bindparam:7,bindtempl:27,binghan:41,birthdai:11,bit:[1,8,14,23,41,45],bite:[29,38],black:41,blade:1,blob:43,block:[1,2,3,4,15,29,36,38],blog:8,blue:2,bondar:43,book:[10,21,43,45],booker:45,bookings_idx_booker_room:45,bookings_idx_day_room:45,bookings_pkei:45,bool:[11,44],booleanproperti:[11,32],boost:[1,7],borrow:[2,15,29,38,39],boss:1,bot:43,both:[2,3,4,10,12,14,21,23,24,26,27,36,41,45],bottleneck:[1,4],bound:[1,21,23,24,26,38,45],boundari:3,branch:8,bridg:3,brien:41,broken:41,browser:8,brutal:1,bryanforb:43,bsd:16,buffer:[3,4],bug:[3,5,10,41,43],bugfix:8,build:[1,3,4,8,10,12,21,23,42],builder:[12,44],built:[3,10,14,16,22,23,24,29,39,41,44,45],builtin:[38,41],bulk:[4,5,29],bunch:6,bundl:21,busi:[1,4,14],bypass:3,c10k:1,c:[1,3,7,8,12,14,44],cach:[7,44],calcul:26,call:[1,2,3,4,7,10,12,15,20,21,23,24,26,27,29,33,36,41,44,45],call_next:10,callabl:[12,26,27,29,33,41],callableload:33,callback:1,caller:10,came:4,can:[1,2,3,4,5,6,7,8,12,13,14,15,21,22,23,24,29,33,36,38,39,41,44,45],candid:41,cannot:[1,2,3,12,41,44],canopi:43,canopytax:43,cap:4,captur:44,care:3,carefulli:1,cast:[11,44],categori:12,categories_1:12,categories_2:12,caught:36,caus:[2,3,4,15,29,38,39,41,45],cd:[8,44],cdi:43,celeri:4,certain:[4,38],chain:[2,4,7,21,23,33,41,45],challeng:4,chanc:[1,4],chang:[3,6,8,10,19,23,24,29,41,44,45],characterist:3,charset:26,chat:4,check:[2,3,8,10,12,14,23,26,27,44,45],checkfirst:[26,27,34],checklist:44,checkout:8,child:[12,41],child_id:12,children:[12,36],chmod:8,choic:[4,10,12],choos:[4,19,41,45],ci:44,cl:[7,11,24],classic:12,classmethod:[7,23,28,33],claus:[2,10,12,14,21,23,26,27,28,29,33,45],clean:[10,41,44],cleaner:[3,44],cleanli:[4,29],cleanup:39,clear:4,cli:44,click:2,client:[4,8,43,44],client_flag:26,clone:8,close:[2,3,4,15,21,22,26,27,28,29,36,38,41,45],closer:3,cmd:44,cn:[8,43],code:[1,2,3,4,10,12,14,15,16,29,36,41,44,45],coin:4,col:12,collect:[23,44],collid:41,color:[2,26,27,28,29],colspec:[26,27],coltyp:[26,27],column:[2,5,6,7,11,12,14,21,23,24,26,27,29,33,38,41,43,44,45],column_kei:27,column_nam:23,columnattribut:24,columnload:[10,12,33],com:[8,10,16,43,44],combin:[2,12,45],come:14,command:[3,4,6,44,45],command_timeout:27,comment:[3,7,29],commit:[0,4,8,15,26,27,28,29,36,41,44],common:[7,14,15,29,39,44],commun:[3,16,43],compani:33,compar:[1,4,23,44],compat:[2,3,10,29,33,41],compil:[7,21,22,28,29,45],compiled_sql:22,complet:[2,10,21,41,44],complex:[1,5,7,12,43,45],compli:3,compliant:3,complic:[0,1],compos:[44,45],composit:23,comput:1,con:0,concentr:4,concept:[2,14,45],conceptu:14,conclus:4,concret:[2,14,24,38],concurr:[1,3,4],condit:[12,23,45],config:[10,13,38,39,41,44],configur:[8,17,33,37,38,44],confirm:41,conflict:[3,12,33],conftest:44,confus:2,congratul:6,conn1:2,conn2:2,conn:[2,3,4,14,21,26,27,28,29,36,41],connect:[0,3,4,5,7,12,14,15,17,19,21,22,26,27,29,34,36,37,38,41,42,44],connect_timeout:26,connection_cl:29,connection_class:27,connectionless:2,conradi:41,consid:[3,4,10,41],consist:41,constant:1,constantli:[3,7],constraint:[24,34,41,45],construct:[4,7,10,14,21,24],consum:[3,4],contain:[3,6,10,24,45],content:[1,17,18],context:[1,2,3,4,10,15,16,21,22,26,27,28,29,33,36,38,39,41,43,44],contextu:[10,12,22,38],contextualgino:10,contextvar:[2,10,17,20,43],continu:[15,36],contrast:[1,4],contribut:[5,41],control:[1,3,5,26,27,29,38,41,44],conv:26,conveni:[2,3,4,14,15,21,23,43,44,45],convers:[2,26,27],convert:33,cool:1,cooper:[0,4],copi:[3,10,29,41,44],core:[1,2,3,5,7,10,16,21,22,24,41,43,45],coroutin:[1,2,3,4,23,29,35,41],correctli:[3,4,14,15,21,23,41,44],correspond:[2,12,22,23,24,26],correspondingli:[2,15,36],cost:1,could:[1,2,3,4,7,8,10,12,22,23,44],count:[2,10,12,44,45],count_1:45,cours:44,cov:44,cover:[41,44,45],coverag:[41,44],cpu:1,cpython:43,creat:[0,3,4,5,7,8,10,12,14,15,19,21,22,23,24,26,27,29,33,34,35,36,38,39,41,42,43],create_al:[3,7,10,11,12,14,21,34,41,45],create_async:[26,27,34],create_async_engin:3,create_engin:[2,3,4,7,10,13,14,19,21,26,29,33,35,41,45],create_ok:34,create_pool:[2,41],create_t:44,create_task:10,createdb:[44,45],creation:[2,7,10,21,22,41,44],credenti:6,credit:8,critic:1,cross:38,crt:8,crucial:15,crud:[2,4,5,10,12,14,17,18,19,21,29,41,42,43],crudmodel:[21,23,41],csrf:44,ctrl:44,ctx:12,cur:44,curatedlist:43,current:[1,2,3,10,13,15,19,23,26,27,29,41,44,45],current_connect:[0,29,41],current_databas:10,current_us:[4,43],cursor:[2,3,26,27,28,29],cursor_cl:[26,27,28],cursorclass:26,custom:[3,5,11,12,23,24,29,41],customiz:[24,41],cut:4,cutil:41,cve:44,d:[4,10,44],dahlia:43,dai:45,daisi:[11,24,43,45],damn:1,danger:38,darwin:44,data:[1,2,3,10,11,12,23,44,45],databas:[0,2,3,5,6,7,8,11,12,14,15,19,21,23,24,26,27,29,33,36,38,39,41,43,44,45],datastructur:44,date:45,datetim:[10,11,12,24,33],datetimeproperti:[11,32],db:[0,2,4,5,7,8,10,11,12,13,14,15,21,23,24,26,27,29,34,36,38,39,41,43,44,45],db_age:23,db_databas:[38,44],db_driver:44,db_dsn:44,db_echo:[10,38,41,44],db_host:[13,38,44],db_kwarg:[13,38],db_name:[6,8],db_pass:8,db_password:[38,44],db_pool_max_s:[38,44],db_pool_min_s:[38,44],db_port:[8,38,44],db_retry_interv:44,db_retry_limit:44,db_ssl:[38,44],db_time:7,db_use_connection_for_request:[38,44],db_user:[8,38,44],dbapi:[26,27,28],dbapi_class:[26,27,28],dbapi_conn:[3,26,27],dbapicursor:[26,27,28],dbname:[10,44],ddl:[34,44],dead:4,deadlock:[3,4],deal:[1,4,10,12,15],debug:[1,38],decent:3,decid:3,decim:41,declar:[1,5,12,17,18,19,21,23,42],declarative_bas:24,declared_attr:[7,11,24,41,45],decod:32,decor:[7,24],decreas:1,deeper:3,def:[1,2,3,4,7,10,11,12,14,24,26,27,38,44,45],default_isolation_level:26,defaultdialect:[26,27],defer:4,defin:[3,5,6,7,11,12,13,14,21,24,29,33,44,45],definit:[10,12,24,44],del:10,delai:4,deleg:[2,14,21,23,41],delet:[12,14,23,41,42,44],delete_us:44,deliv:[1,3],demand:7,demo:44,demonstr:44,depend:[1,2,4,6,7,10,23,29,36,41,42,45],deploi:[4,44],deprec:[3,23,33,41],describ:[4,10,16],descript:[6,8,26,27,28,39,44],design:[2,3,4,10,45],detach:45,detail:[2,8,10,12,45],detect:44,determin:[4,12],deutel:41,dev:[43,44],develop:[6,8,17,38,44],diagram:[1,2,44],dialect:[2,3,10,11,13,16,17,18,19,22,29,34,36,39,41,45],dict:[2,10,11,13,23,24,33,41,44],dictionari:[2,29,38],did:41,didn:[2,4,31],differ:[1,2,3,4,5,11,12,14,21,23,24,29,41,44,45],difficult:1,dig:14,direct:[4,26,29,45],directli:[2,3,4,7,10,12,14,21,23,24,26,29,38,41,44,45],directori:[6,41,44],dirti:[10,41],disabl:[23,41],disable_inherit:41,disable_task_loc:41,disadvantag:4,disallow:10,disast:[15,38],discard:[2,23,29],disconnect:45,discord:43,discourag:4,discuss:10,disproportion:4,dist:41,distinct:[12,23,33,41],distinguish:2,divio:16,django:5,do_load:33,do_on_connect:[26,27],doc:[3,6,8,10,15,41,43,44],docker:[8,44],dockerfil:44,docstr:8,document:[2,3,5,6,7,10,41,43,44,45],doe:[2,3,4,5,12,14,21,23,29,33,41,43,45],doesn:[2,3,4,10,11,12,24,41,44,45],doge:3,don:[0,1,2,3,5,10,12,15,23,29,44,45],done:[0,1,2,6,8,10,14,15,22,41,44,45],doubl:1,doubt:12,down:[4,44],downgrad:[6,44],download:16,dramat:1,driven:8,driver:[2,3,10,15,26,27,29,39,41,45],drivernam:44,drop:[1,34,41,44],drop_al:[3,10,12,34],drop_async:[26,27,34],drop_ok:34,drop_tabl:44,dsn:[39,41,44],due:[4,41,45],dure:[1,2,22,24,29,38,41],dynam:[12,14,24],dziewulski:41,e:[1,2,3,4,6,7,10,12,21,23,26,27,33,38,44,45],each:[1,2,3,4,6,7,10,12,22,24,29,33,38,45],earli:[3,15,16,17,36,39],earlier:[3,10,39],easi:[2,3,10,12],easier:[1,3,7,8,10],easili:[1,2,3,4,12],echo:[2,3,10,29,39,41,44],ecosystem:[3,4],edg:1,edit:11,effect:[19,23,29,45],effici:[1,2],effort:4,either:[1,2,3,4,6,7,10,14,15,23,29,36,44],elem:[21,22,28],element:21,els:[2,3,4,10,14,15,29,38,44,45],email:[4,10],email_address:14,emerg:41,emit:3,empti:[2,38,39,41,44],en:43,enabl:[2,8,10,23,24,33,39,41,44],enable_inherit:41,enable_task_loc:41,encapsul:[3,4,10,44],encod:[32,43],encourag:21,encrypt:8,end:[2,3,4,12,15,44,45],endpoint:4,enforc:[3,12,15],engin:[0,1,3,5,15,17,18,19,21,22,23,26,35,36,39,43,44,45],engine_cl:35,engine_from_config:21,enginestrategi:35,enhanc:[4,8,41],enjoi:38,enough:[4,44],ensur:41,enter:[15,21,29],entri:[14,31,44],entry_point:44,enumer:26,env:[6,10,44],environ:[8,41,44],ep:44,equal:[2,23,41],equival:[23,24,26],error:[3,10,27,28,41],es:24,especi:[1,2,4,10,12,41,45],establish:38,etc:[3,7,26,27,44],even:[1,2,3,4,7,8,10,14,21,23,24,29,38,41,44,45],event:[1,3,4,8,26,27,41,43],eventlet:43,eventu:[2,3,10,44],ever:[3,36],everi:[1,7,8,12,23],everyon:2,everyth:[2,3,4,10,14,15,38,45],evil:3,exactli:[2,12,21,29,45],exampl:[1,2,3,4,6,7,8,10,12,13,14,15,21,23,29,33,36,38,41,44,45],except:[1,2,3,10,15,17,18,19,27,28,29,36,38,39,41],exchangeratesapi:43,excit:3,exec:8,execut:[0,3,4,5,7,11,12,14,21,22,23,26,27,28,29,33,39,41,45],execute_bak:[26,27,28],executemani:[28,29,41],execution_ctx_cl:[26,27],execution_opt:[2,3,7,10,12,21,22,23,26,29,33,41],executioncontextoverrid:[26,27,28],exhaust:[2,4],exist:[2,3,4,5,21,23,24,26,27,39,41,45],exit:[15,21,29,36],exp:11,expect:[3,33],experiment:[12,23,33],explain:[3,4,8,10,12,16,44],explan:[16,43],explicit:[0,2,3,10,12,15,26,41,43,45],explicitli:[2,3,4,7,10,11,36,38,45],explict:14,expos:[14,21,41],exposur:3,express:[5,11,23,29,33],ext:[3,10,13,17,18,19,21,38,39,41,44],extens:[2,7,10,13,14,17,21,31,38,39,41,42,45],extern:[1,45],extra:[1,3,33,41,44],extract:41,extrem:2,f2c273a43a3ed893a767d4239046f2befabf510d:43,f:[12,44,45],face:4,facebook:43,facil:26,fact:[3,10,26,27],factori:[22,24,39,44],fail:[2,21,41,44],fair:1,fals:[2,3,4,7,11,14,23,24,26,27,28,29,34,38,39,41,44],familiar:45,famou:1,fantix:[23,43,44,45],far:[3,14,45],fast:[4,38,43,44],fastapi:[4,10,42,43],faster:[7,22,45],featur:[2,3,5,12,23,33,41,44,45],fed:[7,12,41],feed:[1,29,33,44],feedback:5,feel:[1,3,23,29,44,45],fetch:[1,29],fetchal:3,fetchval:3,few:[2,4,10,29,38,41],field:[10,11,14,23],file:[1,6,8,41,44],fill:33,filter:[23,45],find:[3,4,6,24,31,45],fine:[3,4,10,14],finish:[1,2,4,6,23,29,38,44],first:[1,2,3,4,5,7,8,10,12,14,19,21,23,26,27,28,29,33,38,39,41,44,45],first_connect:[26,27],first_nam:10,first_or_404:41,firstli:12,fit:2,five:41,fix:[3,4,5,10,41,44,45],fixtur:44,flag:[26,27,39],flake8:8,flat:10,flexibl:[11,12,43],flow:4,flush:4,fly:5,focu:10,folder:6,follow:[2,3,4,10,11,15,21,33,36,44,45],footprint:1,forc:[1,4],foreign:[10,12,23],foreignkei:[10,12,14,43],forev:[3,23,29],forget:[2,3,45],fork:8,format:[10,26,38],former:2,fortun:4,forward:[12,26,27,28],found:[2,4,10,12,21,22,24,41,44,45],foundat:[4,43],founder:45,founding_us:45,fouser:21,framework:[1,7,10,41,44],free:[4,16,23,29,44,45],freebsd:43,freez:4,frequent:[5,12],friendli:[41,45],from:[1,2,3,4,6,7,10,11,12,13,14,15,16,21,22,23,24,29,33,36,38,39,41,43,44,45],fromclaus:33,frozen:44,frustrat:3,full:[2,6,10,44],full_path:6,fulli:[29,44,45],fullnam:14,fun:[4,45],func:[10,12,33,45],func_or_elem:[21,22],fundament:[3,4],further:[2,3,7,14,29,45],furthermor:[2,7,29,38],fut:10,futur:[3,10,12,44],g:[1,2,3,4,7,12,21,23,26,27,38,44,45],gain:12,galden:41,garciasilva:43,gbasic:43,gcc:44,gener:[1,2,11,12,23,24,26,27,33,44],geoalchemi:43,get:[1,2,3,4,5,7,10,12,14,16,19,21,22,23,24,29,33,38,41,42,43,44],get_affected_row:[26,28],get_app:44,get_column:33,get_current_connect:41,get_event_loop:45,get_from:33,get_isolation_level:[26,27],get_lastrowid:[26,28],get_loc:41,get_now:2,get_or_404:[38,41,44],get_profil:32,get_raw_connect:29,get_result_proxi:28,get_statusmsg:[26,27,28],get_us:[38,44],get_vers:19,getattr:44,getlogg:44,getter:[7,21],gevent:43,gino:[2,3,4,5,6,8,11,12,13,15,17,18,38,39,42],gino_db:8,gino_fastapi_demo:44,gino_fastapi_demo_test:44,gino_starlett:31,ginoconnect:[2,14,15,17,22,29,36],ginoengin:[2,10,14,15,21,22,23,29,33,35,36,41],ginoexcept:30,ginoexecutor:[2,7,21,22],ginonulltyp:[26,27],ginopool:41,ginoschemavisitor:[14,21,34],ginostrategi:35,ginotransact:[15,29,36,41],git:[8,29,44],github:[8,10,16,43],gitignor:44,gitlab:43,gitter:16,give:[1,3,4,12,22,33],given:[2,3,8,10,12,21,22,23,24,26,27,29,33,35,44],global:[7,14,21,45],gmail:44,gnu:43,go:[1,3,6,14,44,45],goe:45,golden:4,goncharov:41,gone:41,good:[1,3,29],got:1,gotta:1,grab:3,grai:2,grammar:[4,10,41],grandson:12,grant:3,great:[4,6,10],greater:[1,20],greatli:[1,2,3,8],green:[1,2],greenlet:[3,10],greenlet_spawn:3,group_bi:[10,12],guarante:[4,15,29,44],guess:4,guess_model:41,guid:[16,44,45],guidelin:5,gunicorn:44,ha:[1,2,3,4,7,10,12,14,15,19,21,22,23,26,27,29,41,45],hack:[10,44,45],had:[1,14,24],hadn:1,hand:[1,2],handl:[1,2,4,15,29,36,41],handler:38,hang:[3,4],happen:[1,3,4,21,29,45],happi:44,hard:4,hardwar:4,harm:4,has_schema:27,has_sequ:27,has_tabl:[26,27],has_typ:27,hash_:22,haunt:[3,4],have:[1,2,3,4,6,7,10,12,14,15,23,33,36,39,43,44,45],haven:44,head:[2,6,44],heavi:3,hei:[1,4],height:11,help:[0,1,8,15,45],helper:7,henc:21,here:[1,2,3,4,7,8,10,11,12,14,15,23,29,36,41,44,45],herebi:21,hidden:[2,3],hide:[2,41],hierarch:[10,12],hierarchi:44,high:[1,4,7],higher:44,histori:17,hit:4,hmm:2,hold:[1,3,10,15,44],holubakha:41,hong:43,honor:44,hood:[2,23,45],hook:[5,10,21,26,27,31],hoorai:3,host:[26,27,39,44],how:[0,1,2,3,6,8,12,15,16,29,43,45],howev:[1,2,3,4,10,14,21,23,29,41],html:43,http:[1,6,8,10,16,29,43,44],hurri:1,hybrid:4,hyper:1,i:[1,2,3,4,5,12,29,38,43],id:[6,7,10,11,12,14,21,23,24,26,33,38,43,44,45],id_val:10,idea:[3,4],ideal:[1,45],ident:[2,3,12,14,23,41,45],identifi:4,idl:[3,4],ignor:[22,44],im:16,imag:44,imagin:[1,3],immedi:[2,4,7,19,23,29,36],immut:2,impair:1,impl:44,implement:[2,3,5,24,26,33,44,45],implic:14,implicit:[0,3,4,10,15,21,26,41,43],implicitli:[2,4,7,10,14,15,29,36],importantli:4,importlib:44,imposs:4,improv:[1,41],in_:12,in_queri:23,inc:43,includ:[3,8,10,23,39,41,44,45],include_foreign_key_constraint:34,include_rout:44,incomplet:44,increment:23,index:[1,5,12,24,34,43,45],index_on_nam:10,indic:[24,33,45],individu:[2,3,23,44],infer:23,info:44,inform:[2,7,10,14,15,21,22,29,45],inherit:[2,7,10,15,22,23,29,41,45],ini:[6,44],init:[6,26,27,41,44],init_app:[13,38,39,44],init_command:26,init_kwarg:[26,27],init_pool:[26,27,28],initi:[2,3,5,7,11,14,23,24,26,27,33,35,39,44],initializederror:30,inject:[2,14],inlin:[14,21,27,41],inner:[2,15,26,27],ins:14,insert:[3,5,14,15,23,26,29,41,43,45],insid:6,insist:12,inspir:[12,14],instal:[6,8,39,41,42,44],instanc:[2,4,6,7,10,11,12,14,15,21,22,23,24,26,27,29,32,33,35,41,44,45],instant:23,instanti:[14,23,29,33,44],instead:[1,2,3,4,12,14,15,23,24,29,31,41,44,45],integ:[6,7,10,11,12,14,24,43,45],integerproperti:[11,32],integr:[5,38,41,42],intend:24,intens:[1,4],interact:[44,45],interest:3,interfac:[2,3,5,21,33,41],interfaceerror:27,interfer:41,interim:3,interleav:1,intern:[2,10,12,15,22,23,24,36,41],internet:4,interpret:[12,36],interrupt:1,interv:27,introduc:[3,10,12,41],introduct:42,intuit:4,invalid:38,invent:10,invers:44,invert_get:24,invertdict:24,invok:[23,26,27],involv:[10,14,26],io:[29,43],irrelev:21,is_:23,is_local_root:41,isdigit:38,ish:4,isol:[0,2,26,27,29,41],isolation_level:[2,3,26],issu:[3,4,8,10,11,26,41,43,44],item:[12,14,33,34,44],iter:[2,3,10,12,14,21,23,24,26,27,28,29,41,43],its:[1,2,3,4,10,12,21,23,24,26,27,29,33,36,39,41,45],itself:[1,2,3,4,10,12,14,15,24,26,27,44],iuliia:41,j:41,jack:14,java:43,jeff:10,jekel:41,jetbrain:43,jim:41,job:29,join:[5,12,21,23,33,41,43],join_queri:12,join_without_n_plus_1:4,jone:[14,43],json:[5,23,26,27,38,41,44],json_support:[17,18,19,23],jsonb:[11,41],jsonindextyp:26,jsonpathtyp:[26,27,41],jsonproperti:[11,23,32],julio:41,just:[1,2,3,4,6,10,12,14,15,23,24,36,38,41,44,45],k:[3,10,12,44],keep:[1,3,4,8,10,12,26,41,45],kei:[8,10,12,23,24,26,33,41,45],kentoseth:41,kept:[4,33],keyout:8,keyword:[2,3,7,10,12,19,22,23,33,45],kill:[1,4],kind:4,king:[43,44],kinwar:41,know:[1,2,3,4,10,15],knowledg:[12,14,23,45],known:[2,12,44,45],kooten:41,kovalev:41,kubernet:44,kw:[12,26,27,34],kwarg:[19,21,22,23,24,26,27,28,29,34,35,36,39,41],label:[33,41],lacerda:41,lambda:12,larg:[3,4,29],larger:[1,44],last:[1,2,4,12,21,23,36,44,45],last_nam:10,lastli:3,lastrowid:26,later:[1,2,10,12,14,29,39],latest:[23,29,43,44],latter:2,law:4,layer:4,layout:[41,44],lazi:[0,3,10,17,29,37,38,41],lazili:[2,7,19,38],lazy_engin:10,lead:[3,4,11],leaf:12,learn:[2,4,22],least:[1,4,7],leav:[3,29],led:29,left:[2,3,12,23,43],legaci:14,len:12,length:14,lengthi:4,leosussan:43,less:[1,2,4,14],lesson:16,let:[1,2,3,4,7,12,14,15,44,45],level:[0,2,4,7,10,11,14,23,24,26,27,29,45],leverag:[3,4,11],li:41,lib:[8,44],libffi:44,librari:[3,41,43,44,45],licens:[16,43],liebig:4,lifetim:38,lightn:44,lightweight:[6,16],like:[1,2,3,4,6,7,8,10,11,12,14,23,24,29,31,38,39,44,45],likewis:[2,12,33,45],limit:[1,3,4,26,27,28,41,45],line:[2,6],linearli:1,link:[3,4,6,10,44],list:[2,8,10,11,14,29,33,44,45],listen:[3,4,26,27],liter:[1,29],littl:[1,8],live:[1,44],ll:[1,2,4,10,11,12,29,41,44,45],load:[2,3,4,5,7,12,21,23,29,33,41,43,44],load_modul:44,loader:[5,10,17,18,19,21,23,28,29,41,43],lobbi:16,local:[2,6,8,17,44],local_infil:26,localhost:[3,4,7,8,10,12,13,14,33,38,39,44,45],locat:[23,41],lock:[3,4,44],log:[44,45],logger:44,logging_nam:[2,29],logic:[1,3,4,44],login:8,longer:[1,2,4,10,29,41],look:[1,4,8,12,23,39,44],lookup:[23,41],loop:[1,3,10,21,26,27,28,29,35,41,43],lose:29,lost:4,lot:[1,3,4,12],love:4,low:[4,7],lower:[1,3,10,45],luckili:3,m:[1,6,7,8,24,44],made:[3,10,12,14,41,44,45],magic:[2,3,7,12,45],magicstack:[10,43],mai:[1,2,3,4,10,11,12,15,21,22,26,29,38,44,45],main:[2,3,4,6,7,10,12,14,44,45],main_app:6,maintain:[3,4,10,24,43,44],mainten:[3,41],major:4,make:[1,2,3,4,6,7,8,10,12,23,26,27,29,38,44,45],make_express:32,make_url:44,manag:[0,1,3,7,15,21,29,36,38,41,44,45],mandatori:14,mani:[2,3,4,5,8,10,26,27,28,29,41,45],manipul:41,manual:[3,5,10,11,12,14,23,29,36,41,44],map:[2,10,12,14,43,44,45],mapper:10,marissa:10,mark:[4,24,29],martin:41,masonri:44,mass:45,massiv:23,master:43,match:[3,12],matchtyp:26,matter:[2,3,12,14],max:[2,44],max_cacheable_statement_s:27,max_cached_statement_lifetim:27,max_inactive_connection_lifetim:27,max_overflow:4,max_queri:27,max_siz:27,maximum:39,maxsiz:26,mayb:3,me:[1,2,3,4],mean:[1,2,4,12,15,23,24,26,27,29,41,45],meaningless:[2,24],meant:[2,45],meanwhil:[7,23,36,41],meet:8,member:45,memori:[1,2,10,23,24,29,45],mention:[1,2,3,14,44,45],mess:[1,21],messag:[4,41],met:45,meta:3,meta_path:31,metaclass:23,metadata:[2,7,10,14,21,22,23,24,34,41,43,44,45],method:[2,3,4,7,14,21,22,23,24,26,27,29,33,36,41,45],michael:43,middl:[1,39],middlewar:[10,39,41],might:[1,2,3,8],migrat:[5,17,44],mike:4,mimic:3,min:44,min_siz:[2,27],mind:1,minhe:43,minim:[1,14],minimum:4,minsiz:26,misc:41,miser:4,miss:[2,3,41],mission:4,mistak:41,mix:[3,4],mixin:[24,41,45],mkdir:44,mkvirtualenv:8,mock:33,mod:44,mode:[2,3,4,26,27,29,36,39,44],model:[2,4,5,6,7,10,11,14,21,23,24,28,29,33,38,41,42,43],model_base_class:21,model_class:[21,24],model_kei:10,modelload:[10,12,33,41],modern:4,modif:45,modifi:[3,12,44,45],modul:[2,6,10,17,18,41,44],modulenotfounderror:6,moment:[1,2,4],more:[1,2,3,4,7,8,10,12,14,15,21,22,23,24,29,41,45],morgan:41,most:[2,3,4,10,14,15,23,26,29,41,45],mostli:[3,4,23],move:[3,41,45],much:[1,2,3,4,11,14,15,29],multi:1,multiparam:[2,21,28,29,41],multipl:[1,2,5,12,21,22,29,33,41,45],multipleresultsfound:[29,30],multiplex:1,multitask:[0,4],musl:44,must:[1,2,3,4,6,10,14,21,29,33,44,45],mutabl:41,my:44,my_app:6,myapp:44,mydb:44,mydb_test:44,mydialect:[26,27],mykyta:41,mymodel:44,myself:[1,4],mysql:[10,26,41,43,45],mysqlcompil:26,mysqldialect:26,mysqlexecutioncontext:26,mytab:15,mytabl:15,myuser:23,n:[7,26,27,28,39,43],name:[1,2,3,4,6,7,8,10,11,12,14,21,23,24,33,35,38,39,41,43,44,45],name_or_url:35,namespac:31,narrow:8,nativ:[3,11,41],natur:[1,4],neal:41,nearli:1,neat:4,neath:3,necessari:[1,4,26,27,38],necessarili:2,need:[1,2,3,4,6,7,8,10,11,12,14,15,19,21,29,38,39,41,44,45],neither:22,nest:[2,3,5,12,29,33,36],network:[1,2,4],never:[1,4,14,15,29,45],nevertheless:4,new_child:12,new_nam:10,new_names_dict:10,newcom:16,newer:3,newli:[23,26,27,41,45],next:[1,3,4,6,14,26,27,28,29,44],nicknam:[6,14,38,44,45],no_delai:26,no_deleg:21,non:[1,10,13,14,29,41,45],nonam:[6,14,45],none:[2,3,10,12,14,21,22,23,24,26,27,28,29,32,33,34,35,38,39,41,44,45],none_as_non:[17,23,33],nor:22,noresultfound:[29,30],normal:[2,3,4,7,11,12,14,15,21,23,24,36,44,45],nosuchrowerror:30,note:[2,3,12,19,26,29,38,41,42,45],noth:[1,2,3,4,10,15,33,41],notic:[1,2,12],now:[1,2,3,4,6,7,8,10,12,14,15,16,21,29,33,41,44,45],nullabl:[11,14,44],nullpool:[13,27,41],nulltyp:[26,27],number:[2,4,11,23,39,44],numer:[26,28],o:[1,2,4,29,41,43],obj:34,object:[2,3,4,6,7,10,11,12,14,21,22,23,24,26,27,28,29,32,33,34,36,41,43,44,45],objectproperti:[11,32],obviou:[1,4],obvious:[4,41],occasion:[4,21],occur:[1,26,27],odd:3,off:[2,3,4,38,41],offer:[2,14,36,41],offici:[6,8,21,31],often:[21,23,24],oh:1,ok:3,okai:[1,44],olaf:41,old:[3,23,41,43],olexii:41,omit:[12,23],on_claus:[23,33],on_connect:[26,27],onc:[1,2,4,6,10,12,22,24,26,27,29,45],one:[1,2,3,4,7,10,12,14,16,21,23,26,27,28,29,33,41,45],one_or_non:[2,7,14,21,29,41],ones:[2,3,4,21,33,44,45],onli:[1,2,3,4,6,7,12,13,14,15,16,21,22,23,24,26,29,33,36,39,44,45],ons:2,op:[3,20,44],open:[3,6,8,15,29,43],openid:4,opensourc:43,openssl:[8,44],oper:[1,2,4,8,21,23,24,29,41,42,43,44],opposit:[4,29],opt:29,optim:2,option:[1,2,3,7,10,12,13,21,22,23,26,27,29,33,41,44],order:[1,2,3,10,12,23,29,44,45],ordinari:12,org:[6,43],origin:[3,8,14,23],orm:[0,2,3,5,12,16,43,45],orphan:2,orz:43,os:[1,3,4],oss:43,other:[1,2,3,4,5,6,8,13,14,15,21,23,24,29,33,39,41,44,45],otherwis:[24,26,27,29,45],our:[1,4,6,44,45],out:[1,2,4,8,14,24,29,45],outer:[2,12,15,23],outerjoin:[10,12,33,41,43],output:[3,12,14],outsid:[1,45],over:[4,44],overal:[4,12],overhead:[1,4],overlap:1,overload:4,overrid:[7,21,23,24,41,44],overwrit:[3,44],own:[1,2,4,10,12,13,14,44,45],owner:8,p:[8,43],packag:[6,17,18,39,41,43,44],page:1,pai:38,pair:[12,23],parallel:[1,2],param:[21,28,29],paramet:[2,5,7,14,21,22,23,24,26,27,28,29,33,39,41,45],paramref:26,paramstyl:[2,26,28],parent:[2,10,12,14,21,36,41],parent_id:[10,12,41],parents_x_children:12,parentxchild:12,pars:[29,41],part:[1,2,3,4,8,10,26,44,45],partial:[2,10,41],particular:[26,27],particularli:23,pascal:41,pass:[1,7,8,26,27,29,33,39,41,44],passfil:27,passin:8,passiv:41,passout:8,password:[6,8,26,27,39,44],patch:[10,20,41],patch_asyncio:20,patch_schema:34,path:44,pattern:[4,10,12],paus:1,pavol:41,payload:2,pem:8,pend:[3,17,23],peopl:[3,4,12],pep:[3,41,43],per:[3,26,27,29],perform:[1,4,7,10,38],perman:[2,3,29,39,41],persist:3,person:3,peter:43,pg:[41,44],pgcompil:27,pgdialect:[3,27],pgexecutioncontext:27,pgjone:43,phase:44,philip:43,pictur:3,piec:[1,12,45],pip:[6,8,39,41,44,45],place:[3,24,26,29,41],plai:[2,4,21,29],plain:[2,10,43],plain_old_java_object:43,platform:[4,44],pleas:[1,2,3,7,8,10,11,12,14,15,19,21,29,38,41,44,45],plu:[7,21],plugabl:44,pluggi:44,plugin:44,plural:45,poetri:[6,41,44,45],point:[1,3,4,29,31,44],poli:24,pool:[2,3,4,5,7,22,26,27,28,29,38,39,41,44],pool_class:[13,26,27,28],pool_max_s:[39,44],pool_min_s:[39,44],pool_recycl:26,poolev:[26,27],pop_bind:[2,21,41,45],popo:43,popul:[23,45],popular:[44,45],port:[3,4,14,26,27,39,44],posit:[7,23,26,27,29,33],possibl:[1,2,3,4,6,7,8,12,14,22,26,27,33,38,41,44,45],post:[2,3,8,12,44],post_exec:26,postfetch_lastrowid:26,postgi:43,postgr:[6,8,10,35,38,39,44],postgreserror:27,postgresql:[2,3,4,7,8,10,11,12,13,14,21,27,33,35,36,41,43,44,45],postgresqlimpl:44,postprocess:29,potenti:[3,12],power:45,pr:[43,44],practic:[1,3,4,29,45],pre:[3,19,22],prebak:[7,19,26,27],predict:1,preemptiv:1,preexecute_autoincrement_sequ:26,prefer:[4,26,33,38,45],prefetch:41,prefix:35,prepar:[5,6,19,22,26,27,28,29,41],preparedstat:[27,28],present:[2,12,29,41],press:44,pretti:[4,14],prevent:[7,41],previou:[1,2,12,14,21,39,41],previous:[1,2,12,44,45],primari:[23,26,41,45],primary_kei:[6,7,10,11,12,14,21,24,38,43,44,45],primarykeyconstraint:[44,45],primit:3,princip:[3,4],print:[3,5,7,11,12,14,23,43,45],prioriti:[4,44],privkei:8,pro:0,probabl:[2,3],problem:[1,3,4,6,16,41],proce:[26,27],process:[1,2,12,26,27,41,44,45],process_row:28,processor:[12,41],procur:26,produc:[2,12],product:[0,42],profil:[11,32,41],program:[0,2,3,4,12,15,45],program_nam:26,progress:2,prohibit:15,project:[6,8,10,42,45],promis:12,prop_nam:[11,24,32],propag:15,proper:[3,4,22],properli:44,properti:[2,4,5,6,10,12,14,21,22,26,27,28,29,33,36,41,45],propos:8,protect:41,provid:[1,2,3,7,10,11,12,14,15,21,22,23,29,31,33,39,41,44],proxi:[23,26],psql:8,psycopg2:[2,3,44],psycopg:44,publicli:[21,29],pull:5,pure:[10,41,45],push:8,put:[2,3,8,14,36],pwd:8,py:[6,8,10,41,43,44],pycharm:43,pydant:44,pypi:[16,41],pyproject:44,pytest:[8,44],python:[1,2,3,4,6,8,10,11,16,20,24,31,41,42,44,45],pythongino:43,pythonpath:[6,44],q:[10,12,23,33],qbasic:43,qualiti:4,quart:[41,43],queri:[0,4,5,8,11,12,14,17,19,21,22,23,24,26,27,28,29,33,39,43,45],query_cl:22,query_executor:21,query_ext:21,query_param:10,querymodel:23,question:[3,4,5],queue:[3,4,22],quick:[5,44],quickli:[3,10],quit:[2,3,4,10,14,23,44,45],qulaz:41,r:[8,43,44],rais:[2,10,15,21,29,36,41],raise_commit:[15,36],raise_for_statu:44,raise_rollback:[15,36],raiseerr:44,ran:1,randint:[10,12],random:[10,12],rang:[10,12,41],rather:[1,4,10,23,45],raw:[1,2,4,5,12,13,14,22,29,33,41,45],raw_conn:[26,27,28],raw_connect:[29,41],raw_pool:[26,27,28,29,41],raw_transact:[15,26,27,28,36],rdbm:[38,45],re:[2,3,4,7,8,10,12,23,29,41,44],reach:[36,38],reaction:4,read:[1,2,3,10,21,22,23,24,29,45],read_commit:3,read_default_fil:26,read_default_group:26,read_root:4,readabl:1,readi:[1,8,44],readm:[8,41],real:1,realiti:4,realli:1,reason:[3,4,26,27],receiv:[1,3,10,26,27],recent:[2,29,45],recogn:[3,15,23,41],recommend:[2,21,29,38,45],reconsid:4,record:[3,41,45],record_class:27,recov:41,recurs:[12,29],recv:1,red:1,redirect:24,reduc:[12,14,44],refactor:41,refer:[2,10,12,15,16,21,29,45],referenc:[5,29,36,41],reflect:24,refresh:41,regardless:[3,44],regist:1,regular:[3,12,14],reinvent:4,rel:6,relat:[2,10,12,43,44],relationship:[4,5,23,29,41],releas:[2,10,17,26,27,28,29,38,39,45],relev:[21,29,41],reli:4,reliabl:[3,4,44],reload:[23,32,44],remain:[10,41,44,45],rememb:[4,8,23,44,45],remind:4,remov:[2,23,41],renam:41,repeat:1,replac:[2,3,24,33,39,41],repli:4,repo:8,report:5,repositori:44,repr:[26,27,28,29],repres:[10,21,22,29,36,45],represent:41,reproduc:8,req:8,requeijo:41,request:[4,5,10,23,38,39,44],requir:[1,2,3,4,8,14,21,23,24,29,41,44,45],requirements_dev:8,reserv:3,reset:[3,41],reset_loc:41,reskov:41,resourc:[1,2,4],respond:4,respons:[1,12,29,38,39],rest:[11,23,36,38,44,45],restart:44,restor:3,restrict:21,result:[1,2,3,5,12,21,22,24,26,27,29,33,41,45],result_processor:[2,26,27],resultproxi:26,resum:[1,4,21],retri:44,retriev:[2,12,23,42,44],retry_interv:44,retry_limit:44,return_model:[2,21,23,28,29],reus:[0,4,12,15,29,33,38,41],reusabl:[0,29],revamp:41,reveal:3,revers:[2,14,29],revert:[2,41],review:[3,41,44,45],revis:[5,11,44],rewritten:[14,41],rewrot:41,ricardo:43,rich:[12,41],right:[0,3,44],risk:[2,3,14,45],riski:3,rm:8,ro:8,roald:41,role:8,roll:[3,15,29,36],rollback:[3,4,15,26,27,28,29,36,41],roman:41,room:45,root:[4,29,44],rootdir:44,roughli:44,rout:38,router:44,row:[2,3,7,10,12,14,23,26,27,28,29,33,41,45],rowproxi:[2,14,33,41],rsa:8,rst:8,rule:[2,14,15,29,33],run:[1,2,3,4,5,6,7,8,11,12,29,33,38,39,44,45],run_sync:3,run_until_complet:45,runtim:[7,44],rv:44,s:[1,2,3,4,6,7,8,10,11,12,14,22,29,41,43,44,45],sa:[3,7,44],sa_conn:29,sacrific:4,safe:[1,14,29],safe_connect:3,sai:[1,3,4,38,44,45],said:[1,4],same:[1,2,3,4,5,6,7,11,12,14,15,21,23,24,29,33,38,39,41,44,45],sampl:[3,6,44],sanic:[13,17,37,41,43],sanicframework:43,saniti:3,save:[1,2,29,32,44],savepoint:[15,36],scalabl:4,scalar:[2,3,4,7,10,14,21,23,28,29,41,45],scale:1,scenario:[1,2,4,7,12,14,45],scene:[2,3,7,44],schedul:1,schema:[5,6,17,18,19,21,24,26,27,29,43,44,45],schema_ext:21,schema_for_object:29,schema_visitor:21,schemadropp:34,schemagener:34,schemaitem:[21,41],scope:[4,8,38],scott:3,search:1,sec:1,second:[1,2,3,4,12,23,29],secret:44,section:[8,16],secur:[10,44],see:[1,2,3,6,7,8,10,14,15,19,29,44,45],seek:4,seem:[1,14],seen:[3,7],select:[2,3,4,7,10,11,12,14,15,21,23,26,29,33,41,43,45],select_from:[10,12,33],self:[5,10,11,21,23,26,27,29,33,38],send:[3,4,8,27],sens:4,sent:21,sep:12,separ:[4,8,12,15,41,44],sequenc:[2,26,27,34],sequence_nam:27,sequenti:1,sergei:41,seri:[2,29],serializ:3,serv:4,server:[2,4,8,10,29,38,39,41,42,45],server_default:[10,11,12],server_public_kei:26,server_set:27,servic:[43,44],session:[3,4,44],sessionmak:4,set:[2,3,5,7,8,10,12,14,21,22,23,24,26,27,29,33,38,39,41,44,45],set_bind:[2,7,10,21,22,29,41,45],set_except:10,set_isol:26,set_isolation_level:[3,26,27],set_main_opt:44,set_result:10,setattr:[12,33,41],setter:[3,10,12,21],settl:38,setup:[6,8,27,39],sever:[1,2,21,23,29,45],shadow:22,shall:[15,23,38,44],shallow:3,share:[1,2,7,29,38,39,41],sharp:1,shini:44,shortcut:[2,4,12,15,19,21,22,23,29,33,41,44,45],shortest:4,should:[1,2,3,4,8,12,13,21,23,24,26,27,29,31,39,41,44,45],shouldn:[4,41],show:[3,12,44],shown:[2,36],shtrikker:41,shut:44,shutdown:44,side:[2,4,12,29],sign:1,signific:3,silenc:1,simeon:41,similar:[1,2,3,6,7,8,12,14,15,21,23,41,44,45],similarli:[2,12,14,15,23,29,41],simpl:[1,2,4,6,10,14,21,41,42,43,45],simpler:[1,2,45],simplest:33,simpli:[1,2,3,4,10,11,12,21,23,29,33,38,39,44,45],simplic:1,simplif:3,simplifi:[7,44],simul:[2,3,10],sinc:[1,23,41],singl:[1,2,3,4,12,14,23,26,27,29,45],singular:45,situat:[4,29,36],size:[1,44],skip:[15,36,44],sleep:[1,2,4],slice:1,slower:[4,45],small:[1,2],smaller:4,smarter:4,so:[1,2,3,4,7,8,10,12,14,15,21,23,24,26,27,29,31,36,41,44,45],soft:3,softwar:[3,16,43],sole:[26,27],solut:[0,4,31,45],solv:[1,3,4,16],some:[1,2,3,4,6,7,8,10,11,12,14,23,29,39,41,44,45],someth:[1,2,4,12,44],sometim:[2,3,4,38,45],soon:38,sourc:[16,23,29,41,43,44],speak:[2,3,45],special:[12,26,27,38],specif:[2,16,23],specifi:[2,11,12,14,15,23,24,26,27,29,33,39,41,45],split:[1,45],sql:[2,3,4,5,11,12,14,21,22,23,26,27,29,34,41,43,45],sql_mode:26,sqlalchemi:[0,2,4,5,6,7,11,12,14,16,19,21,22,23,24,26,27,29,33,34,35,39,41,43,44,45],sqltype:[26,27],src:[41,43,44],ssl:[5,8,26,27,39,41,44],ssl_cert_fil:8,ssl_key_fil:8,stabil:10,stabl:[3,41,43,45],stack:[2,29,41,44,45],standard:[2,3,4],stare:44,starleet:41,starlett:[10,17,31,37,41,43,44],start:[1,2,3,4,5,7,10,15,16,23,29,36,38,41,42,45],startup:44,starv:0,starvat:4,state:[1,4,45],stateless:[4,10],statement:[2,3,4,5,10,14,15,19,22,23,26,27,28,41,45],statement_cache_s:27,statement_compil:[26,27],statu:[2,3,7,14,15,21,23,28,29,41,45],status_cod:44,stave:4,step:[1,2,3,4,7,8,16,44],stereotyp:4,stick:1,still:[1,2,3,4,10,12,14,22,24,29,33,36,41,44,45],stop:[4,15,38],storag:[24,41],store:[1,2,3,11,12,24,26,44],stori:[0,3],storm:[3,41],str:[4,11,22,33,44],straightforward:4,strang:45,strategi:[2,17,18,19,33],string:[2,7,10,11,12,14,21,22,23,24,26,27,29,38,41,43,45],stringproperti:[11,32],strongli:4,structur:[3,6,12,23,29],stub:43,stuff:[3,45],style:[4,12,14,41],sub:[4,10,24,33,41,44],subclass:[2,14,21,24,33,36,41,45],subj:8,subload:12,submit:[5,44],submodul:[17,18],subpackag:[17,18],subqueri:[23,41],subsequ:3,subset:8,succeed:29,success:[3,6],successfulli:[3,36,45],suffici:3,suffix:7,sugar:4,suggest:[1,45],suit:[23,41],summari:4,support:[2,3,5,8,11,12,16,17,20,21,23,26,29,33,37,41,43,44,45],support_prepar:[26,28],support_return:[26,28],supports_native_decim:[26,27],suppos:[2,7,24,33,36],sure:[1,3,4,8,12,29,38,44],surpris:3,svx:8,swagger:44,sweet:1,sy:31,symbol:21,sync:[3,10],sync_engin:3,sync_safe_connect:3,synchron:[1,21],syntax:8,system:[1,2,8,10,12,44],t1:3,t:[0,1,2,3,5,10,11,12,14,15,22,23,24,29,31,36,41,44,45],tabl:[5,6,7,12,14,15,21,23,24,26,27,34,41,44,45],table_nam:[24,26,27],tableclaus:14,tag:8,take:[1,2,4,7,10,13,14,23,29,33],taken:[1,2,29,38],talk:4,target:[44,45],target_metadata:[6,10,44],task:[1,2,4,17,29,39],tast:[14,44],team:[1,23,41],team_id:23,technic:23,telegram:43,tell:[2,3,45],temporarili:3,ten:[1,4],termin:[8,45],test:[3,8,41,42],test_aiohttp:8,test_crud:44,test_gino:8,test_us:44,testclient:44,text:[3,8,10,11,12,14,21,33,41],textual:[2,12],than:[1,2,3,4,7,10,12,13,15,23,24,29,43,45],thank:[1,2,41],thei:[1,2,3,4,7,8,10,12,14,15,23,24,29,41,44,45],them:[1,3,4,6,7,10,11,12,23,41,44,45],themselv:[1,7],theoret:[4,41],therefor:[1,2,4,7,10,14,21,23,29,38,45],thi:[1,2,3,4,6,7,8,9,10,11,12,14,15,20,21,22,23,24,26,27,29,31,33,35,36,38,39,40,41,44,45],thing:[1,2,3,4,6,7,14,24,41],think:[1,3,4,10],third:[12,14],those:[4,29],though:[1,3,4,14,23,38,45],thought:[3,44],thousand:[1,4],thread:[1,2,3,4],three:2,through:[2,5,8,12,15,21,31,41,44,45],throughput:[1,4],thu:[1,2,4,14,15,29,41],tiago:41,tiangolo:43,tiger:3,tim:43,time:[1,2,3,4,6,10,11,12,15,19,21,23,26,29,38,44],timeout:[7,21,23,26,27,28,29,41],timeouterror:29,timestamp:11,timezon:24,tini:4,tip:5,titl:[43,44],to_dict:[23,41,44],togeth:[1,2,3,29,36,44,45],token:4,toml:44,toni:[41,43],too:[1,2,3,4,10,14,15,29,44,45],took:3,tool:[4,6,43,44,45],toolkit:6,top:[2,3,4,10,14,16,29,44,45],tornado:[17,37,41,43],tortois:43,total:[1,4],touch:[15,36],touchabl:2,tox:8,track:[44,45],trackedmixin:24,tradit:[10,14,45],transact:[2,3,4,5,10,12,17,18,19,21,26,27,28,29,38,43,44],transaction_isol:3,transform:12,transit:3,translat:[2,11,12],trap:[15,36],travers:2,traverse_singl:34,treat:[2,3,7,12,24,29,35],tree:12,tri:[1,4,29,41,45],trigger:[1,4,24],trio:10,troubleshoot:8,truncat:45,ts:33,tupl:[2,10,12,23,29,33],tupleload:[10,12,33,41],turn:[1,2,3,12,29,38,41],tutori:[14,16,43,44,45],twice:[2,5,26,27,45],twist:[4,43],two:[1,2,4,7,10,12,14,15,23,29,36,44,45],tx1:[15,36],tx2:[15,36],tx3:36,tx:[15,27,29,36,41],txt:8,type:[2,5,10,11,12,14,23,24,26,29,33,36,41,45],type_nam:27,typic:[4,10,26],u:[8,10,11,12,23,24,43],ua1:12,ua2:12,ubuntu:43,uc:43,ui:44,uid:[7,12,44],ultim:4,ultra:43,um:1,unbind:21,unchang:45,under:[2,3,10,12,14,21,23,24,33,41,44,45],underli:[2,3,14,15,29,36,41],unfortun:[3,4],unicod:[6,10,12,14,24,26,27,38,44,45],unifi:[2,41],uninitializederror:[10,30,41],uniqu:[2,12,45],unique_constraint:24,unique_id:24,uniqueconstraint:24,unix:43,unix_socket:26,unknown:[12,41],unknownjsonpropertyerror:30,unless:[2,4,23,45],unlik:1,unnam:[41,44],unnecessarili:38,unpin:41,unpredict:[1,4,38],unrecogn:39,unreli:4,unreus:2,unset:2,unspecifi:23,untest:2,until:[1,10,11,29],untouch:45,unus:[2,4],unwant:14,unwrap:[26,27],up:[1,2,5,8,10,12,21,26,27,29,36,39,45],updat:[2,3,5,8,11,23,24,29,33,36,41,42,43,44],update_execution_opt:[29,41],updaterequest:[23,45],upgrad:[2,3,6,10,41,44],upon:[3,15,26,27,36],upstream:[3,10],urh:1,url:[2,6,21,26,27,28,35,39,41,44,45],us:[1,2,3,4,5,8,11,12,13,14,15,21,22,23,24,26,27,29,31,33,35,36,38,39,41,43,44,45],usabl:[2,29,41],usag:[2,5,6,23,29,39,41],use_connection_for_request:[39,44],use_unicod:26,user1:23,user2:23,user:[2,3,4,5,6,7,8,11,12,13,14,21,23,24,26,27,29,33,36,38,39,43,44,45],user_gett:7,user_id:[10,12,14,23,38],user_queri:7,user_t:7,usermodel:44,usernam:[6,44],users_t:2,usual:[1,2,3,4,7,10,11,12,14,21,23,24,29,44,45],utc:12,util:[3,24,31],uuid4:44,uuid:44,uvicorn:[43,44],uvicornwork:44,uvloop:[1,43],v7oze:29,v:[8,10,12,43],val:[10,11,32],valid:[3,14],valu:[2,3,7,10,12,14,15,21,23,24,26,27,29,32,33,38,41,44,45],valueload:33,van:41,vanilla:[2,14],vargovcik:41,vari:44,variabl:[6,43,44,45],variant:2,ve:[1,3,4,12],venv:44,veri:[1,2,3,4,11,12,14,23,26,27,29,36,38],verifi:3,version:[3,6,7,8,10,19,20,21,22,23,24,39,41,44],view:[26,44],virtual:[3,8],virtualenv:44,virtualenvwrapp:8,visit:[10,12,21],visit_foreign_key_constraint:34,visit_index:34,visit_metadata:34,visit_sequ:34,visit_t:34,visual:43,vladimir:41,volkova:41,volunt:8,wa:[1,2,3,4,12,15,26,29,36],wai:[2,3,4,8,10,11,12,14,15,21,29,44,45],wait:[1,3,4,11,15,23,29,44],wang:[41,43],wanna:[1,4],want:[1,2,3,4,5,6,8,14,21,23,29,38,44,45],ware:10,warehous:22,warn:[41,44],wast:[1,2,4],watch:[1,43],we:[1,2,3,4,6,7,10,11,12,14,15,21,38,41,44,45],weakref:41,web:[1,7,8,10,41,43],websit:8,weird:3,welcom:[8,14,41,43],well:[1,2,3,4,24],were:[1,4],what:[1,2,3,4,5,14,15,21,44],whatev:[2,4,12,23,29],wheel:4,when:[0,1,2,3,6,7,8,10,12,14,15,19,20,21,22,23,24,26,29,33,36,38,41,44,45],whenev:44,where:[2,3,4,7,10,11,12,14,21,23,29,33,36,44,45],wherea:1,whether:[8,29,36,41],which:[1,2,3,4,7,10,12,14,21,22,23,24,26,27,29,36,38,41,44,45],whichev:41,whoever:8,whole:[1,41,45],whose:[3,14,36,45],why:[0,1,12,45],wide:[3,26,27],wider:45,wiki:43,wikipedia:43,wip:[9,38,40],wire:3,wise:[3,4],wish:[2,8,12,29],with_bind:[2,7,10,12,14,21,22,41],with_tabl:[7,24,41],within:[1,3,4,15,26,27,29,38,41],without:[1,2,3,4,7,11,14,15,21,29,41],won:[1,2,3,4,7,10,14,15,22,36,41,44,45],wonder:1,word:[3,45],work:[1,2,3,4,5,6,8,12,14,17,23,24,26,29,37,41,44,45],workaround:[3,10],workdir:44,worker:44,world:[1,3,14,38],worri:[2,4,10,15,41],worth:[2,14],would:[1,3,4,8,10,12,21,23,33],wrap:[3,10,24],wrapper:[2,3,4,10,24,29,43],write:[1,2,4,5,10,12,42,45],written:[14,43],wrong:[1,4,41],wrote:[1,4],ww4ronfhiqi:43,www:43,wysiwyg:3,x509:8,x:[3,7,10,28,39,41],xss:44,xxx:[23,43],xxxx:5,yai:[1,10],ye:[1,2,3],yeah:3,year:3,yellow:1,yet:[1,10,11,29],yield:[1,4,14,23,24,29,33,44],yml:44,you:[1,2,3,4,6,7,8,10,11,12,14,15,21,22,23,24,29,36,38,39,41,44,45],your:[1,2,3,4,6,8,10,12,14,15,21,29,41,44,45],your_name_her:8,yourdbnam:44,youtub:43,yurii:41,zen:43,zenof:43,zero:[23,29],zh:43,zone:[11,12]},titles:["Explanation","Asynchronous Programming 101","Engine and Connection","SQLAlchemy 2.0","Why Asynchronous ORM?","How-to Guides","Use Alembic","Bake Queries","Contributing","CRUD","Frequently Asked Questions","JSON Property","Loaders and Relationship","Connection Pool","Schema Declaration","Transaction","Welcome to GINO\u2019s documentation!","Reference","API Reference","gino package","gino.aiocontextvars module","gino.api module","gino.bakery module","gino.crud module","gino.declarative module","gino.dialects package","gino.dialects.aiomysql module","gino.dialects.asyncpg module","gino.dialects.base module","gino.engine module","gino.exceptions module","gino.ext package","gino.json_support module","gino.loader module","gino.schema module","gino.strategies module","gino.transaction module","Extensions","Sanic Support","Starlette Support","Tornado Support","History","Tutorials","\u5b98\u5ba3\uff1aPython \u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668","Build a FastAPI Server","GINO Basics"],titleterms:{"0":[3,41],"02":41,"03":41,"04":41,"05":41,"06":41,"07":41,"08":41,"09":41,"1":[10,41],"10":41,"101":1,"11":41,"12":41,"14":41,"15":41,"16":41,"18":41,"19":41,"2":[3,41],"20":41,"2017":41,"2018":41,"2019":41,"2020":41,"21":41,"23":41,"24":41,"25":41,"26":41,"28":41,"3":41,"4":[10,41],"5":41,"6":41,"7":41,"8":41,"\u4f18\u52bf\u4e0e\u4e0d\u8db3":43,"\u5148\u8bf4":43,"\u5173\u4e8e\u4f5c\u8005":43,"\u518d\u8bf4":43,"\u53c2\u8003\u6587\u732e":43,"\u5b98\u5ba3":43,"\u5efa\u8bbe\u793e\u4f1a\u4e3b\u4e49":43,"\u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668":43,"\u65b9\u4fbf\u5feb\u6377":43,"\u662f\u8c01":43,"\u7b80\u5355\u660e\u4e86":43,"do":10,"new":44,Be:4,Is:10,One:12,The:[1,3],access:4,add:44,admin:10,advanc:12,aiocontextvar:[10,20],aiomysql:26,alemb:[6,10,44],api:[3,7,18,21,41,44],appli:6,ar:7,ask:10,async:3,asynchron:[1,4],asyncio:[4,10],asyncpg:27,auto:3,autocommit:3,avail:7,bake:7,bakedqueri:7,bakeri:[7,22],bare:7,base:28,basic:[15,45],batch:10,bug:8,build:44,bulk:10,can:10,column:10,commit:3,complex:10,complic:3,con:1,configur:39,connect:[2,10,13,39,45],content:[19,25,31],contextvar:41,contribut:8,control:15,cooper:1,core:14,creat:[2,6,11,44,45],crud:[9,23,45],current_connect:2,custom:7,databas:[4,10],db:[3,6],declar:[14,24,45],defin:10,delet:45,depend:44,develop:41,dialect:[25,26,27,28],differ:10,django:10,document:[8,16],doe:10,don:[4,7],done:4,earli:41,engin:[2,7,10,14,29,41],except:30,execut:[2,10],exist:10,explan:0,explicit:4,express:12,ext:31,extens:[37,44],fastapi:44,featur:[8,10],feedback:8,first:6,fix:8,fly:10,frequent:10,get:[8,45],gino:[7,10,14,16,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,41,43,44,45],ginoconnect:41,guid:5,guidelin:8,help:4,histori:41,hook:11,how:[4,5,7,10],i:[7,10],implement:8,implicit:2,index:[10,11],initi:10,insert:10,instal:45,integr:[7,44],interfac:10,introduct:45,isol:3,join:10,json:11,json_support:32,lazi:[2,39],level:3,link:16,load:10,loader:[7,12,33],local:41,manag:2,mani:12,manual:15,migrat:[6,41],model:[12,44,45],modul:[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],multipl:10,multitask:1,nest:15,none_as_non:41,note:44,oper:45,orm:[4,10,14],other:12,packag:[19,25,31],paramet:10,pend:41,pool:13,prepar:7,print:10,pro:1,product:[4,44],program:1,project:44,properti:11,pull:8,python:43,queri:[2,7,10,41],question:10,quick:11,raw:10,refer:[17,18],referenc:12,relationship:[10,12],releas:41,report:8,request:8,result:10,retriev:45,reus:2,reusabl:2,revis:6,right:4,run:10,s:16,same:10,sanic:38,schema:[14,34],self:12,server:44,set:6,simpl:44,solut:3,sql:10,sqlalchemi:[3,10],ssl:10,starlett:39,start:[8,11,44],starv:4,statement:7,stori:1,strategi:35,submit:8,submodul:[19,25],subpackag:19,support:[10,38,39,40],t:[4,7],tabl:10,task:41,test:44,through:10,tip:8,tornado:40,transact:[15,36,41],tutori:42,twice:10,type:8,up:6,updat:[10,45],us:[6,7,10,16],usag:[12,15],user:10,want:7,welcom:16,what:[7,10],when:4,why:4,work:[10,38,39],write:[8,44],xxxx:10}}) \ No newline at end of file diff --git a/docs/en/master/tutorials.html b/docs/en/master/tutorials.html new file mode 100644 index 0000000..186831b --- /dev/null +++ b/docs/en/master/tutorials.html @@ -0,0 +1,242 @@ + + + + + + + + Tutorials - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/tutorials/announcement.html b/docs/en/master/tutorials/announcement.html new file mode 100644 index 0000000..fae2f2f --- /dev/null +++ b/docs/en/master/tutorials/announcement.html @@ -0,0 +1,498 @@ + + + + + + + + 官宣:Python 异步编程再添一利器 - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    官宣:Python 异步编程再添一利器

    +
    +

    GINO 填补了国内外 asyncio ORM 领域的空白

    +
    +

    随着 Tornado1 和 asyncio2 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +GINO 了解一下!

    +../_images/python-gino.webp +
    +

    GINO 是谁

    +

    GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义3手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,你肯定要问一句“这是谁”。

    +

    ORM,即关系对象映射(Object-Relational Mapping4),是一类开发人员喜闻乐见的效率工具,它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢?

    +

    因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 current_user.name +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试?

    +

    传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,但应用到大型项目中却十分考验开发人员的平均水平。

    +

    所有这些问题如果再放进异步编程的环境里,那就是 O(n2) 的复杂度了——哦不,是 +O(2n)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。

    +

    所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,我索性在 1.0 稳定版发布的前夕做个年终总结。

    +
    +
    +

    先说“方便快捷”

    +
    +

    “方便快捷”主要说的是开发效率。

    +
    +

    重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。

    +
    from gino import Gino
    +db = Gino()
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +
    +

    这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟?

    +

    没有错,这就是 SQLAlchemy ORM5 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core6(SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic7、各种 SQLAlchemy 的增强插件8、专业领域的 PostGIS/geoalchemy9等,GINO 全都兼容。

    +

    是不是十分方便、十分快捷?不止这样。

    +

    GINO 一站式地解决了常用 CRUD 快捷方式10、上下文管理(aiocontextvars1112)、 +数据库事务封装和嵌套13、连接池管理和懒加载14等多项便捷功能,无额外依赖关系,即装即用。

    +
    daisy = await User.create(name="daisy")
    +await daisy.update(name="Daisy").apply()
    +
    +
    +

    GINO 还提供了15各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado1、aiohttp16、Sanic17、FastAPI18/Starlette19、Quart20什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。

    +

    为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa21 的降维打击:

    +
      +
    1. 最少侵入型22:SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。

    2. +
    3. 终身不婚型23:天生厌恶“对象”,只愿定义“表”,空手接 SQL。

    4. +
    5. 火力全开型24:最大程度的便利,非典型异步 ORM。

    6. +
    +

    最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、一秒可读百万行的 asyncpg25,以及 uvloop26(可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,深受俄罗斯和乌克兰人民的爱戴。

    +
    +
    +

    再说“简单明了”

    +
    +

    Explicit is better than implicit.

    +

    Simple is better than complex.

    +

    –The Zen of Python, PEP 2027

    +
    +

    Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,因此 GINO 的很多设计都受到了明确性的影响。

    +

    比如说,GINO 的 Model 是完全无状态的普通 Python 对象(POPO28—— 例如前面的 User +类,它的实例 daisy 就是内存里面的常规对象,你可以用 daisy.name 访问属性,也可以用 +daisy.name = "DAISY" 来修改属性,或者用 u = User() 来创建新的实例,这些操作都不会访问数据库,绝对绿色环保无毒副作用。

    +

    等到需要操作数据库的时候,你一定会有感知的。比如执行 INSERT 要用 +u = await User.create(),而 UPDATE 则是 +await u.update(name="Daisy").apply()

    +
    +

    Hint

    +

    其中,u.update(name="Daisy") 与 u.name = "Daisy" 类似,都是只在内存里修改对象的属性,不同的是 u.update() 还会返回一个包含本次变更的中间结果,对其执行 await xxx.apply() 则会将这些变更应用到数据库里。

    +
    +

    这里的 await 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 await +就没有数据库操作。

    +

    另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders29。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 u = await User.get(1) 可以直接获取到 ID 为 1 的用户对象。但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。加载器的用法也是很简单的,比如一个用户可能写了很多本书:

    +
    class Book(db.Model):
    +    __tablename__ = "books"
    +    id = db.Column(db.Integer, primary_key=True)
    +    title = db.Column(db.String)
    +    author_id = db.ForeignKey("users.id")
    +
    +
    +

    然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者:

    +
    query = Book.outerjoin(User).select()
    +loader = Book.load(author=User)
    +async for book in query.gino.load(loader).iterate():
    +    print(book.title, "written by", book.author.name)
    +
    +
    +

    很简单的一个外连接查询 Book.outerjoin(User),配合一个直观的加载器 +Book.load(author=User),就实现了:

    +
      +
    1. 执行 SELECT * FROM books LEFT JOIN users ON ...

    2. +
    3. 将数据库返回结果的每一行中,属于 books 的字段加载成一个 Book 实例;

    4. +
    5. 然后将该行中剩下的属于 users 的字段加载成一个 User 实例;

    6. +
    7. 最后将 User 实例设置到 Book 实例的 author 属性上。

    8. +
    +

    既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。

    +
    +
    +

    优势与不足

    +

    随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM30、ORM31(是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。

    +

    GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,可以谨慎地用于生产环境了。

    +

    以下是近来统计到的关于 GINO 的应用案例:

    + +

    另外,GINO 还贴心地提供了中文文档32,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!):

    +../_images/docs.webp +

    GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。

    +
    +
    +

    建设社会主义

    +

    GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 4888 元的 PyCharm +专业版全家桶 License 一枚33(没有发票)。

    +

    目前急需帮助的有:

    +
      +
    1. 各个 Web 框架插件的维护工作需要多人认领;

    2. +
    3. 更多的例子和文档,以及中文、俄文的翻译;

    4. +
    5. MySQL 的支持。

    6. +
    +

    以及下面这些一直需要的帮助:

    +
      +
    1. 用 GINO,找 bug,提建议;

    2. +
    3. 修 bug,做功能,提 PR;

    4. +
    5. 维护社区,回答问题,参与讨论;

    6. +
    7. 最后也是最重要的:去 GitHub 上给 GINO 加一颗星星!

    8. +
    +../_images/happy-hacking.png +
    +
    +

    关于作者

    +

    87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P

    +

    特别感谢 Tony Wang 对 GINO 项目的贡献,以及所有人的贡献。

    +
    +
    +

    参考文献

    +
    +
    1(1,2)
    +

    Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado.

    +
    +
    2
    +

    Python Software Foundation. asyncio — 异步 I/O. +https://docs.python.org/zh-cn/3/library/asyncio.html.

    +
    +
    3
    +

    维基百科. GNU. https://zh.wikipedia.org/wiki/GNU.

    +
    +
    4
    +

    维基百科. 对象关系映射. +https://zh.wikipedia.org/wiki/对象关系映射.

    +
    +
    5
    +

    维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy.

    +
    +
    6
    +

    Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. +https://docs.sqlalchemy.org/en/13/core/index.html.

    +
    +
    7
    +

    Michael Bayer. Welcome to Alembic’s documentation!. +https://alembic.sqlalchemy.org/en/latest/.

    +
    +
    8
    +

    Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. +https://github.com/dahlia/awesome-sqlalchemy.

    +
    +
    9
    +

    Ricardo GarciaSilva. Does it have postgis support?. +https://github.com/python-gino/gino/issues/627.

    +
    +
    10
    +

    Fantix King. GINO 基础教程 - 增删改查. +https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations.

    +
    +
    11
    +

    Python Software Foundation. contextvars —Context Variables. +https://docs.python.org/3/library/contextvars.html.

    +
    +
    12
    +

    Fantix King. gino/aiocontextvars.py. +https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py.

    +
    +
    13
    +

    Fantix King. 数据库事务. +https://python-gino.org/docs/zh/master/how-to/transaction.html.

    +
    +
    14
    +

    Fantix King. 引擎与连接. +https://python-gino.org/docs/zh/master/explanation/engine.html.

    +
    +
    15
    +

    GINO Community. GINO Community. https://github.com/python-gino/.

    +
    +
    16
    +

    aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/.

    +
    +
    17
    +

    Sanic Community. SanicFramework. https://sanicframework.org/.

    +
    +
    18
    +

    Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/.

    +
    +
    19
    +

    Encode OSS. Starlette. https://www.starlette.io/.

    +
    +
    20
    +

    Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/.

    +
    +
    21
    +

    Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. +https://github.com/CanopyTax/asyncpgsa.

    +
    +
    22
    +

    Fantix King. 表结构定义 -GINO 引擎. +https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine.

    +
    +
    23
    +

    Fantix King. 表结构定义 - GINO core. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core.

    +
    +
    24
    +

    Fantix King. 表结构定义 - GINO ORM. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm.

    +
    +
    25
    +

    MagicStack Inc. +asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. +https://github.com/MagicStack/asyncpg.

    +
    +
    26
    +

    MagicStack Inc. uvloop -Ultra fast asyncio event loop. +https://github.com/MagicStack/uvloop.

    +
    +
    27
    +

    Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/.

    +
    +
    28
    +

    Wikipedia. Plain old Java object. +https://en.wikipedia.org/wiki/Plain_old_Java_object.

    +
    +
    29
    +

    Fantix King. 加载器与关系. +https://python-gino.org/docs/zh/master/how-to/loaders.html.

    +
    +
    30
    +

    Andrey Bondar. Tortoise ORM. https://tortoise.github.io/.

    +
    +
    31
    +

    Encode OSS. ORM - An async ORM. https://github.com/encode/orm.

    +
    +
    32
    +

    Fantix King. 欢迎来到 GINO 的文档!. +https://python-gino.org/docs/zh/master/index.html.

    +
    +
    33
    +

    JetBrains s.r.o. Open Source Licenses. +https://www.jetbrains.com/community/opensource/#support.

    +
    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/tutorials/fastapi.html b/docs/en/master/tutorials/fastapi.html new file mode 100644 index 0000000..4257a89 --- /dev/null +++ b/docs/en/master/tutorials/fastapi.html @@ -0,0 +1,744 @@ + + + + + + + + Build a FastAPI Server - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Build a FastAPI Server

    +

    In this tutorial, we’ll build a production-ready FastAPI server together. +The full functional example is available here.

    +

    Our application stack will look like this:

    +../_images/gino-fastapi.svg
    +

    Start a New Project

    +

    Instead of pip, let’s use the shiny Poetry to manage our project. Follow the link to +install Poetry, and create our new +project in an empty directory:

    +
    $ mkdir gino-fastapi-demo
    +$ cd gino-fastapi-demo
    +$ git init
    +$ poetry init
    +
    +
    +

    Then follow the Poetry guide to finish the initialization - you may say “no” to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains gino-fastapi-demo.

    +
    +
    +

    Add Dependencies

    +

    FastAPI is built on top of the Starlette framework, so we shall use the GINO +extension for Starlette. Simply run:

    +
    $ poetry add gino[pg,starlette]
    +
    +
    +

    Then let’s add FastAPI, together with the lightning-fast ASGI server Uvicorn, and +Gunicorn as a production application server:

    +
    $ poetry add fastapi uvicorn gunicorn
    +
    +
    +

    For database migration, we’ll use Alembic. Because it uses normal DB-API, we need +psycopg here too:

    +
    $ poetry add alembic psycopg2
    +
    +
    +

    At last, let’s add pytest in the development environment for testing. We also want to +add the requests library to use the Starlette TestClient:

    +
    $ poetry add -D pytest requests
    +
    +
    +
    +

    Hint

    +

    With the steps above, Poetry will automatically create a virtualenv for you +behind the scene, and all the dependencies are installed there. We will assume +using this for the rest of the tutorial. But you’re free to create your own +virtualenv, and Poetry will honor it when it’s activated.

    +
    +

    That’s all, this is my pyproject.toml created by Poetry, yours should look similar:

    +
    [tool.poetry]
    +name = "gino-fastapi-demo"
    +version = "0.1.0"
    +description = ""
    +authors = ["Fantix King <fantix.king@gmail.com>"]
    +
    +[tool.poetry.dependencies]
    +python = "^3.8"
    +gino = {version = "^1.0", extras = ["pg", "starlette"]}
    +fastapi = "^0.54.1"
    +uvicorn = "^0.11.3"
    +gunicorn = "^20.0.4"
    +alembic = "^1.4.2"
    +psycopg2 = "^2.8.5"
    +
    +[tool.poetry.dev-dependencies]
    +pytest = "^5.4.1"
    +requests = "^2.23.0"
    +
    +[build-system]
    +requires = ["poetry>=0.12"]
    +build-backend = "poetry.masonry.api"
    +
    +
    +../_images/gino-fastapi-poetry.svg

    And there’s also an auto-generated poetry.lock file with the frozen versions. The +directory layout should look like the diagram on the right. Now let’s add the two files +to the Git repository (we will skip showing these git operations in future steps):

    +
    $ git add pyproject.toml poetry.lock
    +$ git commit -m 'add project dependencies'
    +
    +
    +
    +
    +

    Write a Simple Server

    +

    Now let’s write some Python code.

    +

    We’ll create an extra src directory to include all the Python files, as demonstrated +in the diagram below. This is known as the “src layout” providing a cleaner hierarchy.

    +../_images/gino-fastapi-src.svg

    The root Python package of our project is named as gino_fastapi_demo, under which we +will create two Python modules:

    +
      +
    • asgi as the ASGI entry point - we’ll feed it to the ASGI server

    • +
    • main to initialize our server

    • +
    +

    Here’s main.py:

    +
    from fastapi import FastAPI
    +
    +def get_app():
    +    app = FastAPI(title="GINO FastAPI Demo")
    +    return app
    +
    +
    +

    And we’ll simply instantiate our application in asgi.py:

    +
    from .main import get_app
    +
    +app = get_app()
    +
    +
    +

    Then run poetry install to link our Python package into the PYTHONPATH in +development mode. We’ll be able to start a Uvicorn development server after that:

    +
    $ poetry install
    +Installing dependencies from lock file
    +
    +No dependencies to install or update
    +
    +  - Installing gino-fastapi-demo (0.1.0)
    +
    +$ poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    +INFO:     Started reloader process [53010]
    +INFO:     Started server process [53015]
    +INFO:     Waiting for application startup.
    +INFO:     Application startup complete.
    +
    +
    +

    The --reload option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server.

    +
    +

    Hint

    +

    As mentioned previously, if you’re in your own virtualenv, the command poetry run +uvicorn can be simplified as just uvicorn.

    +

    poetry run is a convenient shortcut to run the following command in the +virtualenv managed by Poetry.

    +
    +
    +
    +

    Add GINO Extension

    +../_images/gino-fastapi-config.svg

    Now let’s add GINO to our server.

    +

    First of all, we need a way to configure the database. In this tutorial, we’ll use the +configuration system from Starlette. +Add src/gino_fastapi_demo/config.py as follows:

    +
    from sqlalchemy.engine.url import URL, make_url
    +from starlette.config import Config
    +from starlette.datastructures import Secret
    +
    +config = Config(".env")
    +
    +DB_DRIVER = config("DB_DRIVER", default="postgresql")
    +DB_HOST = config("DB_HOST", default=None)
    +DB_PORT = config("DB_PORT", cast=int, default=None)
    +DB_USER = config("DB_USER", default=None)
    +DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    +DB_DATABASE = config("DB_DATABASE", default=None)
    +DB_DSN = config(
    +    "DB_DSN",
    +    cast=make_url,
    +    default=URL(
    +        drivername=DB_DRIVER,
    +        username=DB_USER,
    +        password=DB_PASSWORD,
    +        host=DB_HOST,
    +        port=DB_PORT,
    +        database=DB_DATABASE,
    +    ),
    +)
    +DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1)
    +DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16)
    +DB_ECHO = config("DB_ECHO", cast=bool, default=False)
    +DB_SSL = config("DB_SSL", default=None)
    +DB_USE_CONNECTION_FOR_REQUEST = config(
    +    "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True
    +)
    +DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1)
    +DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1)
    +
    +
    +

    This config file will load from environment variable first, if not found then from a +file named .env from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this:

    +
    $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +
    +
    +../_images/gino-fastapi-env.svg

    Or set them in the file .env (this file must not be committed into Git, remember to +add it to .gitignore):

    +
    DB_HOST=localhost
    +DB_USER=postgres
    +
    +
    +

    Now it’s time to create a PostgreSQL database and set the connection variables +correctly here. This is usually something like createdb yourdbname, but it may vary +across different platforms, so we won’t cover this part in this tutorial.

    +
    +

    Tip

    +

    Alternatively, you could also set DB_DSN to for example +postgresql://user:password@localhost:5432/dbname to override the other individual +config values like DB_HOST defined before DB_DSN.

    +

    If defined, DB_DSN always have the higher priority over the individual ones, +regardless of where they are defined - even if DB_HOST is defined in environment +variable and DB_DSN is defined in .env file, DB_HOST is still ignored. +Default value doesn’t count.

    +
    +../_images/gino-fastapi-models.svg

    Then, create a new Python sub-package gino_fastapi_demo.models to encapsulate +database-related code, and add the code below to +src/gino_fastapi_demo/models/__init__.py:

    +
    from gino.ext.starlette import Gino
    +
    +from .. import config
    +
    +db = Gino(
    +    dsn=config.DB_DSN,
    +    pool_min_size=config.DB_POOL_MIN_SIZE,
    +    pool_max_size=config.DB_POOL_MAX_SIZE,
    +    echo=config.DB_ECHO,
    +    ssl=config.DB_SSL,
    +    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    +    retry_limit=config.DB_RETRY_LIMIT,
    +    retry_interval=config.DB_RETRY_INTERVAL,
    +)
    +
    +
    +

    At last, modify src/gino_fastapi_demo/main.py to install the GINO extension:

    +
     from fastapi import FastAPI
    ++
    ++from .models import db
    +
    + def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    ++    db.init_app(app)
    +     return app
    +
    +
    +

    Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database:

    +
    WARNING:  Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading...
    +INFO:     Shutting down
    +INFO:     Waiting for application shutdown.
    +INFO:     Application shutdown complete.
    +INFO:     Finished server process [63562]
    +INFO:     Started server process [63563]
    +INFO:     Waiting for application startup.
    +INFO:     Connecting to the database: postgresql://fantix:***@localhost
    +INFO:     Database connection pool created: <asyncpg.pool.Pool max=16 min=1 cur=1 use=0>
    +INFO:     Application startup complete.
    +
    +
    +
    +
    +

    Create Models and API

    +../_images/gino-fastapi-models-users.svg

    It’s time to implement the API now. Let’s say we are building a user management service, +through which we could add users, list users and delete users.

    +

    First of all, we need a database table users to store the data, mapped to a GINO +model named User. We shall add the model in gino_fastapi_demo.models.users:

    +
    from . import db
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default="unnamed")
    +
    +
    +../_images/gino-fastapi-views.svg

    The model definition is simple enough to explain itself.

    +

    Then we only have to use it properly in the API implementation, for which we’ll create a +new Python sub-package gino_fastapi_demo.views, and a new module +gino_fastapi_demo.views.users as follows:

    +
    from fastapi import APIRouter
    +from pydantic import BaseModel
    +
    +from ..models.users import User
    +
    +router = APIRouter()
    +
    +@router.get("/users/{uid}")
    +async def get_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    return user.to_dict()
    +
    +class UserModel(BaseModel):
    +    name: str
    +
    +@router.post("/users")
    +async def add_user(user: UserModel):
    +    rv = await User.create(nickname=user.name)
    +    return rv.to_dict()
    +
    +@router.delete("/users/{uid}")
    +async def delete_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    await user.delete()
    +    return dict(id=uid)
    +
    +def init_app(app):
    +    app.include_router(router)
    +
    +
    +

    The APIRouter holds our new APIs locally, and init_app is used to integrate it +into our FastAPI application. Here we want some inversion of control: let’s make the +APIs plugable, so that we don’t have to import all possible future views manually. We +shall use the Entry Points feature to load the dependencies. Add this code below to +gino_fastapi_demo.main:

    +
    import logging
    +from importlib.metadata import entry_points
    +
    +logger = logging.getLogger(__name__)
    +
    +def load_modules(app=None):
    +    for ep in entry_points()["gino_fastapi_demo.modules"]:
    +        logger.info("Loading module: %s", ep.name)
    +        mod = ep.load()
    +        if app:
    +            init_app = getattr(mod, "init_app", None)
    +            if init_app:
    +                init_app(app)
    +
    +
    +
    +

    Hint

    +

    If you’re running Python < 3.8, you’ll need this importlib-metadata backport.

    +
    +

    And call it in our application factory:

    +
     def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    +     db.init_app(app)
    ++    load_modules(app)
    +     return app
    +
    +
    +

    Finally, define the entry points in pyproject.toml following the Poetry document +for plugins:

    +
    [tool.poetry.plugins."gino_fastapi_demo.modules"]
    +"users" = "gino_fastapi_demo.views.users"
    +
    +
    +

    Run poetry install again to activate the entry points - you may need to restart the +Uvicorn development server manually, as the reloader cannot capture the changes we made +to pyproject.toml.

    +

    Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven’t created the database tables.

    +
    +
    +

    Integrate with Alembic

    +

    To get started with Alembic, run this command in the project root directory:

    +
    $ poetry run alembic init migrations
    +
    +
    +../_images/gino-fastapi-alembic.svg

    This will generate a new directory migrations where Alembic will store database +migration revisions. At the same time, an alembic.ini file is created in the project +root directory. Let’s simply add all of them to Git control.

    +

    For Alembic to use our data models defined with GINO (and of course the database +config), we need to modify migrations/env.py to connect with the GINO instance:

    +
     # add your model's MetaData object here
    + # for 'autogenerate' support
    + # from myapp import mymodel
    + # target_metadata = mymodel.Base.metadata
    +-target_metadata = None
    ++from gino_fastapi_demo.config import DB_DSN
    ++from gino_fastapi_demo.main import db, load_modules
    ++
    ++load_modules()
    ++config.set_main_option("sqlalchemy.url", str(DB_DSN))
    ++target_metadata = db
    +
    +
    +

    Then create our first migration revision with:

    +
    $ poetry run alembic revision --autogenerate -m 'add users table'
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.autogenerate.compare] Detected added table 'users'
    +  Generating migrations/versions/32c0feba61ea_add_users_table.py ...  done
    +
    +
    +

    The generated revision file should roughly look like this:

    +
    def upgrade():
    +    op.create_table(
    +        "users",
    +        sa.Column("id", sa.BigInteger(), nullable=False),
    +        sa.Column("nickname", sa.Unicode(), nullable=True),
    +        sa.PrimaryKeyConstraint("id"),
    +    )
    +
    +def downgrade():
    +    op.drop_table("users")
    +
    +
    +
    +

    Hint

    +

    Whenever there is a change to the database schema in the future, just modify the +GINO models and run alembic revision --autogenerate again to generate new +revisions to track the change. Remember to review the revision file - you may want +to adjust it.

    +
    +

    Eventually, let’s apply this migration, by upgrading to the latest revision:

    +
    $ poetry run alembic upgrade head
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.runtime.migration] Running upgrade  -> 32c0feba61ea, add users table
    +
    +
    +

    Now all the APIs should be fully operational, try with the Swagger UI.

    +
    +
    +

    Write the Tests

    +

    In order not to break our development database with running tests, let’s create a +separate database to run tests. Apply this change to gino_fastapi_demo.config:

    +
     config = Config(".env")
    +
    ++TESTING = config("TESTING", cast=bool, default=False)
    +
    + DB_DRIVER = config("DB_DRIVER", default="postgresql")
    + DB_HOST = config("DB_HOST", default=None)
    + DB_PORT = config("DB_PORT", cast=int, default=None)
    + DB_USER = config("DB_USER", default=None)
    + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    + DB_DATABASE = config("DB_DATABASE", default=None)
    ++if TESTING:
    ++    if DB_DATABASE:
    ++        DB_DATABASE += "_test"
    ++    else:
    ++        DB_DATABASE = "gino_fastapi_demo_test"
    + DB_DSN = config(
    +
    +
    +
    +

    Hint

    +

    You need to run createdb to actually create the database. If you have set +DB_DATABASE in .env - e.g. DB_DATABASE=mydb, the name of the testing +database should be mydb_test. Or else, gino_fastapi_demo_test.

    +
    +

    Then, let’s create our pytest fixture in tests/conftest.py:

    +
    import pytest
    +from alembic.config import main
    +from starlette.config import environ
    +from starlette.testclient import TestClient
    +
    +environ["TESTING"] = "TRUE"
    +
    +@pytest.fixture
    +def client():
    +    from gino_fastapi_demo.main import db, get_app
    +
    +    main(["--raiseerr", "upgrade", "head"])
    +
    +    with TestClient(get_app()) as client:
    +        yield client
    +
    +    main(["--raiseerr", "downgrade", "base"])
    +
    +
    +../_images/gino-fastapi-tests.svg

    This fixture creates all the database tables before running the test, yield a Starlette +TestClient, and drop all the tables with all the data after the test to maintain a +clean environment for the next test.

    +

    Here’s a sample test in tests/test_users.py:

    +
    import uuid
    +
    +def test_crud(client):
    +    # create
    +    nickname = str(uuid.uuid4())
    +    r = client.post("/users", json=dict(name=nickname))
    +    r.raise_for_status()
    +
    +    # retrieve
    +    url = f"/users/{r.json()['id']}"
    +    assert client.get(url).json()["nickname"] == nickname
    +
    +    # delete
    +    client.delete(url).raise_for_status()
    +    assert client.get(url).status_code == 404
    +
    +
    +

    Then run the test:

    +
    $ poetry run pytest
    +=========================== test session starts ===========================
    +platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    +rootdir: gino-fastapi-demo
    +collected 1 item
    +
    +tests/test_users.py .                                               [100%]
    +
    +============================ 1 passed in 1.21s ============================
    +
    +
    +
    +
    +

    Notes for Production

    +

    Given the popularity of Docker/Kubernetes, we’ll build a Dockerfile for our demo:

    +
    FROM python:3.8-alpine as base
    +
    +FROM base as builder
    +RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev
    +RUN pip install poetry
    +COPY . /src/
    +WORKDIR /src
    +RUN python -m venv /env && . /env/bin/activate && poetry install
    +
    +FROM base
    +RUN apk add --no-cache postgresql-libs
    +COPY --from=builder /env /env
    +COPY --from=builder /src /src
    +WORKDIR /src
    +CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"]
    +
    +
    +

    In this Dockerfile, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn with +UvicornWorker from Uvicorn as the worker class for best production reliability.

    +

    Let’s review what we have in the project.

    +../_images/gino-fastapi-layout.svg

    This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live:

    +
      +
    • Set DB_RETRY_LIMIT to a larger number to allow staring the application server +before the database is fully ready.

    • +
    • Implement the same retry logic in migrations/env.py so that Alembic gets the same +functionality.

    • +
    • Enable DB_SSL if needed.

    • +
    • Write a docker-compose.yml for other developers to get a quick taste or even use +it for development.

    • +
    • Enable CI, install pytest-cov and use --cov-fail-under to guarantee coverage.

    • +
    • Integrate static code analysis tools and security/CVE checking tools.

    • +
    • Automate Alembic upgrade properly - e.g. after new version is deployed.

    • +
    • Be aware of the common security attacks like CSRF, XSS, etc.

    • +
    • Write load tests.

    • +
    +

    Again, the source code of the demo is available here, +and the source of this tutorial is here. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking!

    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/master/tutorials/tutorial.html b/docs/en/master/tutorials/tutorial.html new file mode 100644 index 0000000..6b8d581 --- /dev/null +++ b/docs/en/master/tutorials/tutorial.html @@ -0,0 +1,607 @@ + + + + + + + + GINO Basics - GINO 1.1.0rc1 documentation + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    GINO Basics

    +

    This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of:

    + +

    Knowledge of SQLAlchemy is not required.

    +
    +

    Introduction

    +

    Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API.

    +

    You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won’t make your +code run faster, if not slower. Please read Why Asynchronous ORM? for more +information.

    +
    +
    +

    Installation

    +

    To install GINO, run this command in your terminal:

    +
    $ pip install gino
    +
    +
    +

    This is the preferred method to install GINO, as it will always install the +most recent stable release.

    +

    If you don’t have pip installed, this Python installation guide can guide +you through the process.

    +

    Alternatively, if you are using Poetry to manage your project dependencies, +you may want to run:

    +
    $ poetry add gino
    +
    +
    +
    +
    +

    Declare Models

    +

    First of all, we’ll need a Gino object, usually under the +name of db as a global variable:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +

    db acts like a reference to the database, most database interactions will +go through it.

    +

    “Model” is a basic concept in GINO, it is a Python class inherited from +db.Model. Each Model +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let’s declare a model:

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +

    By declaring this User class, we are actually defining a database table +named users, with two columns id and nickname. Note that the fixed +__tablename__ property is required. GINO +suggests singular for model names, and plural for table names. Each +db.Column property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to db types here in the SQLAlchemy +documentation.

    +
    +

    Note

    +

    SQLAlchemy is a powerful ORM library for non-asynchronous programming in +Python, on top of which GINO is built. SQLAlchemy supports many popular +RDBMS including PostgreSQL and MySQL through different dialect +implementation, so that the same Python code can be compiled into different +SQL depending on the dialect you choose. GINO inherited this support too, +but for now there is only one dialect for PostgreSQL through asyncpg.

    +
    +

    If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:

    +
    class Booking(db.Model):
    +    __tablename__ = 'bookings'
    +
    +   day = db.Column(db.Date)
    +   booker = db.Column(db.String)
    +   room = db.Column(db.String)
    +
    +   _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey')
    +   _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True)
    +   _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room')
    +
    +
    +

    It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +here in the +SQLAlchemy documentation.

    +

    Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the __table_args__ attribute. In order +to e.g. define constraints in mixin classes, +declared_attr() is required. Please feel free to read +more about it in its API documentation.

    +
    +
    +

    Get Connected

    +

    The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let’s create a +PostgreSQL database for this tutorial:

    +
    $ createdb gino
    +
    +
    +

    Then we tell our db object to connect to this database:

    +
    import asyncio
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +

    If this runs successfully, then you are connected to the newly created database. +Here postgresql indicates the database dialect to use (the default driver +is asyncpg, you can explicitly specify that with postgresql+asyncpg://, +or simply asyncpg://), localhost is where the server is, and gino +is the name of the database. Check here for more +information about how to compose this database URL.

    +
    +

    Note

    +

    Under the hood set_bind() calls +create_engine() and bind the engine to this db object. GINO +engine is similar to SQLAlchemy engine, but not identical. Because GINO +engine is asynchronous, while the other is not. Please refer to the API +reference of GINO for more information.

    +
    +

    Now that we are connected, let’s create the table in database (in the same +main() method):

    +
    await db.gino.create_all()
    +
    +
    +
    +

    Warning

    +

    It is db.gino.create_all, +not db.create_all, because +db is inherited from SQLAlchemy MetaData, +and db.create_all is from +SQLAlchemy using non-asynchronous methods, which doesn’t work with the +bound GINO engine.

    +

    In practice create_all() is usually +not an ideal solution. To manage database schema, tool like Alembic is +recommended, please see how to Use Alembic.

    +
    +

    If you want to explicitly disconnect from the database, you can do this:

    +
    await db.pop_bind().close()
    +
    +
    +

    Let’s review the code we have so far together in one piece before moving on:

    +
    import asyncio
    +from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +    await db.gino.create_all()
    +
    +    # further code goes here
    +
    +    await db.pop_bind().close()
    +
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +
    +
    +

    CRUD Operations

    +

    In order to operate on the database, one of GINO’s core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations.

    +
    +

    Create

    +

    Let’s start by creating a User:

    +
    user = await User.create(nickname='fantix')
    +# This will cause GINO to execute this SQL with parameter 'fantix':
    +# INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname
    +
    +
    +

    As mentioned previously, user object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:

    +
    print(f'ID:       {user.id}')           # 1
    +print(f'Nickname: {user.nickname}')     # fantix
    +
    +
    +

    It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:

    +
    user = User(nickname='fantix')
    +user.nickname += ' (founder)'
    +await user.create()
    +
    +
    +
    +
    +

    Retrieve

    +

    To retrieve a model object from database by primary key, you can use the class +method get() on the model class. Now let’s retrieve +the same row:

    +
    user = await User.get(1)
    +# SQL (parameter: 1):
    +# SELECT users.id, users.nickname FROM users WHERE users.id = $1
    +
    +
    +

    Normal SQL queries are done through a class property +query. For example, let’s retrieve all User +objects from database as a list:

    +
    all_users = await db.all(User.query)
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +

    Alternatively, you can use the gino extension on +query. This has exactly the same effect as above:

    +
    all_users = await User.query.gino.all()
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +
    +

    Note

    +

    User.query is actually a SQLAlchemy query, with its own +non-asynchronous execution methods. GINO added this gino extension on +all executable SQLAlchemy clause objects to conveniently execute them in +the asynchronous way, so that it is even not needed to import the db +reference for execution.

    +
    +

    Now let’s add some filters. For example, find all users with ID lower than 10:

    +
    founding_users = await User.query.where(User.id < 10).gino.all()
    +# SQL (parameter: 10):
    +# SELECT users.id, users.nickname FROM users WHERE users.id < $1
    +
    +
    +

    Read more here +about writing queries, because the query object is exactly from SQLAlchemy core.

    +
    +

    Warning

    +

    Once you get a model object, it is purely in memory and fully detached from +the database. That means, if the row is externally updated, the object +values remain unchanged. Likewise, changes made to the object won’t affect +the database values.

    +

    Also, GINO keeps no track of model objects, therefore getting the same row +twice returns two different object with identical values. Modifying one +does not magically affect the other one.

    +

    Different than traditional ORMs, the GINO model objects are more like +objective SQL results, rather than stateful ORM objects. In order to adapt +for asynchronous programming, GINO is designed to be that simple. That’s +also why GINO Is Not ORM.

    +
    +

    Sometimes we want to get only one object, for example getting the user by name +when logging in. There’s a shortcut for this scenario:

    +
    user = await User.query.where(User.nickname == 'fantix').gino.first()
    +# SQL (parameter: 'fantix'):
    +# SELECT users.id, users.nickname FROM users WHERE users.nickname = $1
    +
    +
    +

    If there is no user named “fantix” in database, user will be None.

    +

    And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +select() class method:

    +
    name = await User.select('nickname').where(User.id == 1).gino.scalar()
    +# SQL (parameter: 1):
    +# SELECT users.nickname FROM users WHERE users.id = $1
    +print(name)  # fantix
    +
    +
    +

    Or get the count of all users:

    +
    population = await db.func.count(User.id).gino.scalar()
    +# SQL:
    +# SELECT count(users.id) AS count_1 FROM users
    +print(population)  # 17 for example
    +
    +
    +
    +
    +

    Update

    +

    Then let’s try to make some modifications. In this example we’ll mixin some +retrieve operations we just tried.

    +
    # create a new user
    +user = await User.create(nickname='fantix')
    +
    +# get its name
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +assert name == user.nickname  # they are both 'fantix' before the update
    +
    +# modification here
    +await user.update(nickname='daisy').apply()
    +# SQL (parameters: 'daisy', 1):
    +# UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname
    +print(user.nickname)  # daisy
    +
    +# get its name again
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +print(name)  # daisy
    +assert name == user.nickname  # they are both 'daisy' after the update
    +
    +
    +

    So update() is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +apply() call makes the update happen in database.

    +
    +

    Note

    +

    GINO explicitly split the in-memory update and SQL update into two methods: +update() and +apply(). update() +will update the in-memory model object and return an +UpdateRequest object which contains all the +modifications. A following apply() on +UpdateRequest object will apply these recorded +modifications to database by executing a compiled SQL.

    +
    +
    +

    Tip

    +

    UpdateRequest object has another method named +update() which works the same as the one +on model object, just that it combines the new modifications together with +the ones already recorded in current UpdateRequest +object, and it returns the same UpdateRequest object. +That means, you can chain the updates and end up with one +apply(), or make use of the +UpdateRequest object to combine several updates in a +batch.

    +
    +

    update() on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the update() on model class level, with a +bit difference:

    +
    await User.update.values(nickname='Founding Member ' + User.nickname).where(
    +    User.id < 10).gino.status()
    +# SQL (parameter: 'Founding Member ', 10):
    +# UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2
    +
    +name = await User.select('nickname').where(
    +    User.id == 1).gino.scalar()
    +print(name)  # Founding Member fantix
    +
    +
    +

    There is no UpdateRequest here, everything is again +SQLAlchemy clause, its +documentation here for +your reference.

    +
    +
    +

    Delete

    +

    At last. Deleting is similar to updating, but way simpler.

    +
    user = await User.create(nickname='fantix')
    +await user.delete()
    +# SQL (parameter: 1):
    +# DELETE FROM users WHERE users.id = $1
    +print(await User.get(user.id))  # None
    +
    +
    +
    +

    Hint

    +

    Remember the model object is in memory? In the last print() +statement, even though the row is already deleted in database, the object +user still exists with its values untouched.

    +
    +

    Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):

    +
    await User.delete.where(User.id > 10).gino.status()
    +# SQL (parameter: 10):
    +# DELETE FROM users WHERE users.id > $1
    +
    +
    +

    With basic CRUD, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking!

    +
    +
    +
    + + +
    +

    + + Creative Commons License + +
    + This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + You're on the master branch. This documentation may include + functionality that is not released yet. + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8d5ecc3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,20 @@ + + + + + + Page Redirection + + + + If you are not redirected automatically, follow this link to GINO docs. + + + diff --git a/docs/versions.json b/docs/versions.json new file mode 100644 index 0000000..57ffd8d --- /dev/null +++ b/docs/versions.json @@ -0,0 +1 @@ +{"en":["1.0","1.1b2","master"],"zh":["1.0","1.1b2","master"]} \ No newline at end of file diff --git a/docs/zh/1.0/.buildinfo b/docs/zh/1.0/.buildinfo new file mode 100644 index 0000000..47deb59 --- /dev/null +++ b/docs/zh/1.0/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 1712d237b0bfd25c158a1de4eb1d4eb7 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/zh/1.0/_images/263px-Minimum-Tonne.svg.png b/docs/zh/1.0/_images/263px-Minimum-Tonne.svg.png new file mode 100644 index 0000000..f431e31 Binary files /dev/null and b/docs/zh/1.0/_images/263px-Minimum-Tonne.svg.png differ diff --git a/docs/zh/1.0/_images/archlinux.webp b/docs/zh/1.0/_images/archlinux.webp new file mode 100644 index 0000000..3e358ef Binary files /dev/null and b/docs/zh/1.0/_images/archlinux.webp differ diff --git a/docs/zh/1.0/_images/community.svg b/docs/zh/1.0/_images/community.svg new file mode 100644 index 0000000..dae6507 --- /dev/null +++ b/docs/zh/1.0/_images/community.svg @@ -0,0 +1,26 @@ + + + + co-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/connection.png b/docs/zh/1.0/_images/connection.png new file mode 100644 index 0000000..e54598d Binary files /dev/null and b/docs/zh/1.0/_images/connection.png differ diff --git a/docs/zh/1.0/_images/docs.webp b/docs/zh/1.0/_images/docs.webp new file mode 100644 index 0000000..20259b1 Binary files /dev/null and b/docs/zh/1.0/_images/docs.webp differ diff --git a/docs/zh/1.0/_images/engine.png b/docs/zh/1.0/_images/engine.png new file mode 100644 index 0000000..6b64561 Binary files /dev/null and b/docs/zh/1.0/_images/engine.png differ diff --git a/docs/zh/1.0/_images/exchangeratesapi.webp b/docs/zh/1.0/_images/exchangeratesapi.webp new file mode 100644 index 0000000..dc2657a Binary files /dev/null and b/docs/zh/1.0/_images/exchangeratesapi.webp differ diff --git a/docs/zh/1.0/_images/explanation.svg b/docs/zh/1.0/_images/explanation.svg new file mode 100644 index 0000000..ef775d1 --- /dev/null +++ b/docs/zh/1.0/_images/explanation.svg @@ -0,0 +1,19 @@ + + + + ex-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-alembic.svg b/docs/zh/1.0/_images/gino-fastapi-alembic.svg new file mode 100644 index 0000000..0cfc4b1 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-alembic.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    alembic.ini
    ale...
    migrations
    migra...
    env.py
    env...
    versions
    versi...
    32c0feba61ea_add_users_table.py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-config.svg b/docs/zh/1.0/_images/gino-fastapi-config.svg new file mode 100644 index 0000000..673d084 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-config.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    config.py
    con...
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-env.svg b/docs/zh/1.0/_images/gino-fastapi-env.svg new file mode 100644 index 0000000..4ec9e4e --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-env.svg @@ -0,0 +1,3 @@ + + +
    .env
    .env
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-layout.svg b/docs/zh/1.0/_images/gino-fastapi-layout.svg new file mode 100644 index 0000000..07f4701 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-layout.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    .env
    .env
    models
    models
    __init__.py
    __i...
    users.py
    use...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    __init__.py
    __i...
    asgi.py
    asg...
    config.py
    con...
    main.py
    mai...
    tests
    tests
    conftest.py
    con...
    test_users.py
    tes...
    migrations
    migra...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    The project root directory.

    Alembic data directory.

    Database migration revisions directory.

    One of the revisions.

    Alembic Python environment.

    Application source code container.

    Project root python package.

    Database models and GINO instance.

    GINO instance (SQLAlchemy Metadata).

    Models for users.

    API implementation.



    User-related APIs.



    ASGI entry point.

    Starlette-style application configuration.

    Application initialization.

    Testing code.

    pytest fixtures.

    User-related tests.

    Local config, not in Git control.

    Alembic entry config.

    Production Docker image.

    Poetry-frozen dependency versions.

    Project and dependency definition.
    The project root directory....
    alembic.ini
    ale...
    Dockerfile
    Doc...
    env.py
    env...
    versions
    versi...
    32c0feba61ea....py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-models-users.svg b/docs/zh/1.0/_images/gino-fastapi-models-users.svg new file mode 100644 index 0000000..886d581 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-models-users.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-models.svg b/docs/zh/1.0/_images/gino-fastapi-models.svg new file mode 100644 index 0000000..4dc983c --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-models.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-poetry.svg b/docs/zh/1.0/_images/gino-fastapi-poetry.svg new file mode 100644 index 0000000..e0da8c1 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-poetry.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-src.svg b/docs/zh/1.0/_images/gino-fastapi-src.svg new file mode 100644 index 0000000..7310163 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-src.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    __init__.py
    __i...
    asgi.py
    asg...
    main.py
    mai...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-tests.svg b/docs/zh/1.0/_images/gino-fastapi-tests.svg new file mode 100644 index 0000000..53673d8 --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-tests.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    tests
    tests
    test_users.py
    tes...
    conftest.py
    con...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi-views.svg b/docs/zh/1.0/_images/gino-fastapi-views.svg new file mode 100644 index 0000000..7321a7a --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi-views.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/gino-fastapi.svg b/docs/zh/1.0/_images/gino-fastapi.svg new file mode 100644 index 0000000..9d365ba --- /dev/null +++ b/docs/zh/1.0/_images/gino-fastapi.svg @@ -0,0 +1,3 @@ + + +
    Gunicorn
    Gunicorn
    Uvicorn
    Uvicorn
    Starlette
    Starlette
    FastAPI
    FastAPI
    API Implementation
    API Implementation
    GINO Models
    GINO Models
    GINO with Starlette
    GINO with Starlette
    SQLAlchemy core
    SQLAlchemy core
    asyncpg
    asyncpg
    Alembic
    Alembic
    psycopg2
    psycopg2
    PostgreSQL
    PostgreSQL
    Application Server
    Application Server
    ASGI Middleware
    ASGI Middleware
    Web Framework
    Web Framework
    Tutorial Code
    Tutorial Code
    GINO
    GINO
    Database Library
    Database Library
    HTTP API
    HTTP API
    DB Migration CLI
    DB Migration CLI
    Legend
    Legend
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.0/_images/github.svg b/docs/zh/1.0/_images/github.svg new file mode 100644 index 0000000..cb2484b --- /dev/null +++ b/docs/zh/1.0/_images/github.svg @@ -0,0 +1,27 @@ + + + + sourcecode + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/happy-hacking.png b/docs/zh/1.0/_images/happy-hacking.png new file mode 100644 index 0000000..701e7bd Binary files /dev/null and b/docs/zh/1.0/_images/happy-hacking.png differ diff --git a/docs/zh/1.0/_images/how-to.svg b/docs/zh/1.0/_images/how-to.svg new file mode 100644 index 0000000..7461024 --- /dev/null +++ b/docs/zh/1.0/_images/how-to.svg @@ -0,0 +1,22 @@ + + + + guide + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/open-source.svg b/docs/zh/1.0/_images/open-source.svg new file mode 100644 index 0000000..0946f0a --- /dev/null +++ b/docs/zh/1.0/_images/open-source.svg @@ -0,0 +1,18 @@ + + + + bsd + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/python-gino.webp b/docs/zh/1.0/_images/python-gino.webp new file mode 100644 index 0000000..f6c11ac Binary files /dev/null and b/docs/zh/1.0/_images/python-gino.webp differ diff --git a/docs/zh/1.0/_images/python.svg b/docs/zh/1.0/_images/python.svg new file mode 100644 index 0000000..f62b29b --- /dev/null +++ b/docs/zh/1.0/_images/python.svg @@ -0,0 +1,22 @@ + + + + download + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/reference.svg b/docs/zh/1.0/_images/reference.svg new file mode 100644 index 0000000..0c5f656 --- /dev/null +++ b/docs/zh/1.0/_images/reference.svg @@ -0,0 +1,19 @@ + + + + re-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/tutorials.svg b/docs/zh/1.0/_images/tutorials.svg new file mode 100644 index 0000000..2eb2111 --- /dev/null +++ b/docs/zh/1.0/_images/tutorials.svg @@ -0,0 +1,26 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_images/why_coroutine.png b/docs/zh/1.0/_images/why_coroutine.png new file mode 100644 index 0000000..99c743a Binary files /dev/null and b/docs/zh/1.0/_images/why_coroutine.png differ diff --git a/docs/zh/1.0/_images/why_multicore.png b/docs/zh/1.0/_images/why_multicore.png new file mode 100644 index 0000000..c209a9f Binary files /dev/null and b/docs/zh/1.0/_images/why_multicore.png differ diff --git a/docs/zh/1.0/_images/why_multithreading.png b/docs/zh/1.0/_images/why_multithreading.png new file mode 100644 index 0000000..e0e4321 Binary files /dev/null and b/docs/zh/1.0/_images/why_multithreading.png differ diff --git a/docs/zh/1.0/_images/why_single_task.png b/docs/zh/1.0/_images/why_single_task.png new file mode 100644 index 0000000..54fab08 Binary files /dev/null and b/docs/zh/1.0/_images/why_single_task.png differ diff --git a/docs/zh/1.0/_images/why_throughput.png b/docs/zh/1.0/_images/why_throughput.png new file mode 100644 index 0000000..388f533 Binary files /dev/null and b/docs/zh/1.0/_images/why_throughput.png differ diff --git a/docs/zh/1.0/_sources/explanation.rst.txt b/docs/zh/1.0/_sources/explanation.rst.txt new file mode 100644 index 0000000..6d91357 --- /dev/null +++ b/docs/zh/1.0/_sources/explanation.rst.txt @@ -0,0 +1,7 @@ +Explanation +=========== + +.. toctree:: + :glob: + + explanation/* diff --git a/docs/zh/1.0/_sources/explanation/async.rst.txt b/docs/zh/1.0/_sources/explanation/async.rst.txt new file mode 100644 index 0000000..a857170 --- /dev/null +++ b/docs/zh/1.0/_sources/explanation/async.rst.txt @@ -0,0 +1,214 @@ +Asynchronous Programming 101 +============================ + + +The Story +--------- + +Let's say we want to build a search engine. We'll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this: + +.. image:: ../images/why_single_task.png + :align: center + +We have lots of web pages to index, so we simply handle them one by one: + +.. image:: ../images/why_throughput.png + :align: center + +Let's assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores: + +.. image:: ../images/why_multicore.png + :align: center + +This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading: + +.. image:: ../images/why_multithreading.png + :align: center + +Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What's wrong with multi-threading? From the diagram we can see: + +* There are yellow bars taking up extra time. +* The green bars can still overlap with any bar in the other thread, but +* non-green bars cannot overlap with non-green bars in the other thread. + +The yellow bars are time taken by `context switches +`_, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let's assume a world without +`Hyper-threading `_ or similar), +so in order to run several threads concurrently the CPU must `split its +time `_ into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point. + +Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it's waiting for the HTTP response (I/O). That's how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won't be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That's also why this is called concurrency instead of parallelism. + +As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous `C10k +problem `_, usually solved by +asynchronous I/O: + +.. image:: ../images/why_coroutine.png + :align: center + +.. note:: + + Asynchronous I/O and coroutines are two different things, but they usually + go together. Here we will stick with coroutines for simplicity. + +Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem. + + +Cooperative multitasking +------------------------ + +So what is a coroutine? + +In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread. + +Threads are scheduled by the operating system using an approach called `preemptive +multitasking `_. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn't +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this: + +.. code-block:: none + + Thread 1: I wanna run! + OS: Okay, here you go... + Thread 2: I wanna run! + OS: Urh, alright one sec ... Thread 1, hold on for a while! + Thread 1: Well I'm not done yet, but you are the boss. + OS: It won't be long. Thread 2 it's your turn now. + Thread 2: Yay! (&%#$@..+*&#) + Thread 1: Can I run now? + OS: Just a moment please ... Thread 2, give it a break! + Thread 2: Alright ... but I really need the CPU. + OS: You'll have it later. Thread 1, hurry up! + +In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don't - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +`cooperative multitasking +`_. It's like this: + +.. code-block:: none + + Coroutine 1: Let me know when event A arrives. I'm done here before that. + Event manager: Okay. What about you, coroutine 2? + Coroutine 2: Um I've got nothing to do here before event B. + Event manager: Cool, I'll be watching. + Event manager: (after a while) Hey coroutine 1, event A is here! + Coroutine 1: Awesome! Let me see ... looks good, but I need event C now. + Event manager: Very well. Seems event B arrived just now, coroutine 2? + Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done. + Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while. + (silence) + Event manager: Damn, event C timed out! + Coroutine 1: Arrrrh gotta kill myself with an exception :S + Event manager: Up to you :/ + +For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That's why in the last diagram, +the red bars are not interleaved like threads. + +.. tip:: + + In Python and asyncio, ``async def`` declares coroutines, ``await`` yields + control to event loop (event manager). + + +Pros and cons +------------- + +Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput. + +With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code. + +However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple ``recv()`` operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to ``recv()``, repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop_ this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O. + +Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, ``sleep(1)`` +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between ``await`` finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind. + +Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It's not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure. + + +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/zh/1.0/_sources/explanation/engine.rst.txt b/docs/zh/1.0/_sources/explanation/engine.rst.txt new file mode 100644 index 0000000..ebd59ed --- /dev/null +++ b/docs/zh/1.0/_sources/explanation/engine.rst.txt @@ -0,0 +1,521 @@ +===================== +Engine and Connection +===================== + +:class:`~gino.engine.GinoEngine` is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together: + +.. image:: ../images/engine.png + :align: center + +Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users. + +During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use. + +.. note:: + + In SQLAlchemy, database drivers are supposed to follow the DB-API standard, + which does not usually provide a pool implementation. Therefore, SQLAlchemy + has its own pool implementation, created directly in engine. This is where + this diagram doesn't fit SQLAlchemy. + +The pool creates raw connections, not the :class:`~gino.engine.GinoConnection` +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries. + +On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use. + +.. note:: + + Another difference to SQLAlchemy here: GINO execution methods always return + final results, while in SQLAlchemy accessing the result may cause further + implicit database accesses. Therefore GINO engine immediately releases the + connection when the execution method on the engine returns, but SQLAlchemy + can only release the connection implicitly when the result data is found + exhausted. + + By immediately releasing a connection, GINO may not release the related raw + connection when the raw connection was reused from another parent + connection. We'll get to this later. + +GINO also supports `implicit execution +`_ +without having to specify an engine or connection explicitly. This is done by +binding the engine to the ``db`` instance, also known as the +:class:`~sqlalchemy.schema.MetaData` or the :class:`~gino.api.Gino` instance. +You may possibly bind a :class:`~gino.engine.GinoConnection` instance, but that +is greatly not recommended because it is very much untested. + +At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +``db`` instance on creation, therefore the models can do implicit execution too +if their ``db`` has a bind. + +Then let's get to some details. + + +Creating Engines +---------------- + +GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous :class:`~gino.engine.GinoEngine` is +just ``gino``, but only available after ``gino`` is imported:: + + import gino, sqlalchemy + + async def main(): + e = await sqlalchemy.create_engine('postgresql://...', strategy='gino') + # e is a GinoEngine + +.. tip:: + + Please read `this SQLAlchemy document + `_ + to learn about writing database URLs. + +Also the GINO strategy replaces the default driver of dialect ``postgresql://`` +from ``psycopg2`` to ``asyncpg``, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +``postgresql+asyncpg://...`` or just ``asyncpg://...``. + +GINO also offers a shortcut as :func:`gino.create_engine`, which only sets the +default strategy to ``gino`` and does nothing more. So here is an identical +example:: + + import gino + + async def main(): + e = await gino.create_engine('postgresql://...') + # e is also a GinoEngine + +As you may have noticed, when using the GINO strategy, +:func:`~sqlalchemy.create_engine` returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default. + +For it is just SQLAlchemy :func:`~sqlalchemy.create_engine`, the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!): + +For Dialect: + +* `isolation_level `_ +* `paramstyle `_ + +For Engine: + +* `echo `_ +* `execution_options `_ +* `logging_name `_ + +While these parameters are discarded by GINO: + +* `module `_ + +In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from :func:`~asyncpg.pool.create_pool`. +For example, we can create an engine without initial connections:: + + e = await gino.create_engine('postgresql://...', min_size=0) + +Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:: + + import sqlalchemy + + metadata = sqlalchemy.MetaData() + metadata.bind = 'postgresql://...' + + # or in short + + metadata = sqlalchemy.MetaData('postgresql://...') + +This implicitly calls :func:`~sqlalchemy.create_engine` under the hood. However +in GINO, creating an engine requires ``await``, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass :class:`~gino.api.Gino`, reverted it to simple assignment:: + + import gino + + db = gino.Gino() + + async def main(): + # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind + engine = await gino.create_engine('postgresql://...') + db.bind = engine + +And provided a shortcut to do so:: + + engine = await db.set_bind('postgresql://...') + +And another simpler shortcut for one-time usage:: + + db = await gino.Gino('postgresql://...') + +To unset a bind and close the engine:: + + engine, db.bind = db.bind, None + await engine.close() + +Or with a shortcut correspondingly:: + + await engine.pop_bind().close() + +Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +Managing Connections +-------------------- + +With a :class:`~gino.engine.GinoEngine` at hand, you can acquire connections +from the pool now:: + + conn = await engine.acquire() + +Don't forget to release it after use:: + + await conn.release() + +Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:: + + async with engine.acquire() as conn: + # play with the connection + +Here ``conn`` is a :class:`~gino.engine.GinoConnection` instance. As mentioned +previously, :class:`~gino.engine.GinoConnection` is mapped to an underlying raw +connection, as shown in following diagram: + +.. image:: ../images/connection.png + :align: center + +Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: ``reuse`` and +``lazy``. They are keyword arguments on :meth:`~gino.engine.GinoEngine.acquire` +and by default switched off. + +reuse +""""" + +When acquiring a :class:`~gino.engine.GinoConnection` (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +:class:`~gino.engine.GinoConnection` (2). This is the default behavior of +:meth:`~gino.engine.GinoConnection.acquire` with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:: + + async with engine.acquire() as conn1: + async with engine.acquire() as conn2: + # conn2 is a completely different connection than conn1 + +But sometimes ``conn2`` may exist in a different method:: + + async def outer(): + async with engine.acquire() as conn1: + await inner() + + async def inner(): + async with engine.acquire() as conn2: + # ... + +And we probably wish ``inner`` could reuse the same raw connection in +``outer`` to save some resource, or borrow a new one if ``inner`` is +individually called without ``outer``:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(conn2=None): + if conn2 is None: + async with engine.acquire() as conn2: + # ... + else: + # the same ... again + +This is exactly the scenario ``reuse`` could be useful. We can simply tell the +:meth:`~gino.engine.GinoConnection.acquire` to reuse the most recent reusable +connection in current context by setting ``reuse=True``, as presented in this +identical example:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(): + async with engine.acquire(reuse=True) as conn2: + # ... + +Back to previous diagram, the blue :class:`~gino.engine.GinoConnection` +instances (3, 4, 6) are "reusing connections" acquired with ``reuse=True``, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that ``acquire(reuse=True)`` always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack. + +.. tip:: + + By context, we are actually referring to the context concept in + `contextvars `_ the + new module in Python 3.7, and its partial backport `aiocontextvars + `_. Simply speaking, you may + treat a series of function calls in a chain as in the same context, even if + there is an ``await``. It's something like a thread local in asyncio. + +:class:`~gino.engine.GinoConnection` (2) may be created through +``acquire(reuse=True)`` too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection. + +Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +:class:`~gino.engine.GinoConnection` can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more. + +lazy +"""" + +As you may have found, :class:`~gino.engine.GinoConnection` (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set ``lazy=True`` on acquire. + +A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, :class:`~gino.engine.GinoConnection` (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + +On implementation level, ``lazy`` is extremely easy in +:meth:`~gino.engine.GinoEngine.acquire`: if ``lazy=False`` then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, :class:`~gino.egnine.GinoConnection` will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all :class:`~gino.engine.GinoConnection` +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + await conn.release(permanent=False) # release (8) + await asyncio.sleep(10) # simulate long I/O work + await conn.scalar('select now()') # re-acquire a new raw connection, + # not necessarily the same (8) + +When used together with ``reuse``, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set ``lazy=False`` on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again: + +.. image:: ../images/connection.png + :align: center + + +reusable +"""""""" + +Usually, you don't have to worry about the two options ``reuse`` and ``lazy``, +using the default :meth:`~gino.engine.GinoEngine.acquire` will always create +a concrete :class:`~gino.engine.GinoConnection` with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use ``reusable=False`` on acquire. As shown in the diagram, the unreusable +:class:`~gino.engine.GinoConnection` is an orphan away from any stack:: + + async with engine.acquire(): # (2) + async with engine.acquire(reusable=False): # the unreusable connection + async with engine.acquire(reuse=True): # (3) + +Unreusable connections can be lazy. But it is usually meaningless to specify +both ``reuse=True`` and ``reusable=False`` at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +``acquire(reuse=True, reusable=False)`` unless you know what it does. + + +current_connection +"""""""""""""""""" + +Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +:attr:`~gino.engine.GinoEngine.current_connection` which is always the reusable +:class:`~gino.engine.GinoConnection` at the top of current stack, or ``None`` +if current stack is empty. + +.. tip:: + + The different between :attr:`~gino.engine.GinoEngine.current_connection` + and :meth:`acquire(reuse=True) ` is, the + latter always produces a :class:`~gino.engine.GinoConnection`, while the + former may not. + + +Executing Queries +----------------- + +Once you have a :class:`~gino.engine.GinoConnection` instance, you can start +executing queries with it. There are 6 variants of the execute method: +:meth:`~gino.engine.GinoConnection.all`, +:meth:`~gino.engine.GinoConnection.first`, +:meth:`~gino.engine.GinoConnection.one`, +:meth:`~gino.engine.GinoConnection.one_or_none`, +:meth:`~gino.engine.GinoConnection.scalar` and +:meth:`~gino.engine.GinoConnection.status`. They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results: + +* :meth:`~gino.engine.GinoConnection.all` returns all results in a + :class:`list`, which may be empty when the query has no result, empty but + still a :class:`list`. +* :meth:`~gino.engine.GinoConnection.first` returns the first result directly, + or ``None`` if there is no result at all. There is usually some optimization + behind the scene to efficiently get only the first result, instead of loading + the full result set into memory. +* :meth:`~gino.engine.GinoConnection.one` returns exactly one result. If there + is no result at all or if there are multiple results, an exception is raised. +* :meth:`~gino.engine.GinoConnection.one_or_none` is similar to + :meth:`~gino.engine.GinoConnection.one`, but it returns ``None`` if there is + no result instead or raising an exception. +* :meth:`~gino.engine.GinoConnection.scalar` is similar to + :meth:`~gino.engine.GinoConnection.first`, it returns the first value of the + first result. Quite convenient to just retrieve a scalar value from database, + like ``NOW()``, ``MAX()``, ``COUNT()`` or whatever generates a single value. + ``None`` is also returned when there is no result, it is up to you how to + distinguish no result and the first value is ``NULL``. +* :meth:`~gino.engine.GinoConnection.status` executes the query and discard all + the query results at all. Instead it returns the execution status line as it + is, usually a textual string. Note, there may be no optimization to only + return the status without loading the results, so make your query generate + nothing if you don't want any result. + +By "result", I meant :class:`~sqlalchemy.engine.RowProxy` of SQLAlchemy - an +immutable row instance with both :class:`tuple` and :class:`dict` interfaces. +Database values are translated twice before they are eventually stored in a +:class:`~sqlalchemy.engine.RowProxy`: first by the database driver (dialect) +from network payload to Python objects (see `Type Conversion +`_ of +how asyncpg does this), second by SQLAlchemy +:meth:`~sqlalchemy.types.TypeEngine.result_processor` depending on the actual +type and dialect. + +The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy :meth:`~sqlalchemy.engine.Connection.execute` (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +``multiparams``, all 4 methods will always return ``None`` discarding all +results. Likewise, the parameter values are processed twice too: first by +:meth:`~sqlalchemy.types.TypeEngine.bind_processor` then the database driver. + +GINO also supports SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execution_options` provided either on +:meth:`engine level `, +:meth:`connection level ` or on +:meth:`queries `. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling ``return_model`` and providing a ``model`` will make +:meth:`~gino.engine.GinoConnection.all` and +:meth:`~gino.engine.GinoConnection.first` return ORM model instance(s) instead +of :class:`~sqlalchemy.engine.RowProxy` instance(s). See also +:meth:`~sqlalchemy.engine.Connection.execution_options` for more information. + +In addition, GINO has an :meth:`~gino.engine.GinoConnection.iterate` method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a `server-side cursor +`_. + + +Implicit Execution +------------------ + +Acquire a :class:`~gino.engine.GinoConnection` and execute queries on it, that +is the most explicit way. You can also execute queries on a +:class:`~gino.engine.GinoEngine` instance. In this case, a connection will be +acquired with ``reuse=True`` for you implicitly, and released after returning:: + + await engine.scalar('select now()') + +Equals to:: + + async with engine.acquire(reuse=True) as conn: + await conn.scalar('select now()') + +This allows you to easily write connectionless code. For example:: + + async def get_now(): + return await engine.scalar('select now()') + + async def main(): + async with engine.acquire(): + now = await get_now() + await engine.status('UPDATE ...') + +In this example, ``main()`` will take only one raw connection. ``get_now()`` +can also work alone out of any ``acquire()`` context, thanks to ``reuse``. + +Furthermore, GINO provides the same query APIs on :class:`~gino.api.Gino` +directly. They are simply delegates to corresponding API methods on the +``bind``. This allows even engine-less programming:: + + db = gino.Gino() + + async def get_now(): + return await db.scalar('select now()') + + async def main(): + async with db.with_bind('postgresql://...'): + now = await get_now() + await db.status('UPDATE ...') + +.. note:: + + In this example we didn't put the two queries in an ``acquire()`` block, so + they might be executed in two different connections. + +At last, the SQLAlchemy `implicit execution +`_ +on queries also work in GINO, under an extension named ``gino``:: + + await users_table.select().gino.all() + +By default, the extension :class:`~gino.api.GinoExecutor` is injected on +:class:`~sqlalchemy.sql.expression.Executable` as a property of name ``gino`` +at the creation of :class:`~gino.api.Gino` instance. Therefore, any +:class:`~sqlalchemy.sql.expression.Executable` object has the ``gino`` +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the ``bind`` of the ``db`` instance. diff --git a/docs/zh/1.0/_sources/explanation/why.rst.txt b/docs/zh/1.0/_sources/explanation/why.rst.txt new file mode 100644 index 0000000..77455b5 --- /dev/null +++ b/docs/zh/1.0/_sources/explanation/why.rst.txt @@ -0,0 +1,244 @@ +Why Asynchronous ORM? +===================== + +Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly. + + +When asyncio Helps +------------------ + +As Mike Bayer - the author of SQLAlchemy - `pointed out +`__, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM". + +The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +:doc:`async`. So the ultimate reason to use asyncio should be the application itself, +not the database. + +For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead. + +.. image:: ../images/263px-Minimum-Tonne.svg.png + :align: right + +Another example is authentication using `OpenID Connect Authorization Code Flow +`__ as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +`Liebig's law `__, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave. + +Arbitrary delays may happen in the database too. In PostgreSQL, you can |LISTEN|_ to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case. + +In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is: + +.. |LISTEN| replace:: ``LISTEN`` +.. _LISTEN: https://www.postgresql.org/docs/current/sql-listen.html + + +How to Access Database +---------------------- + +The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here. + +Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:: + + import asyncio + from fastapi import FastAPI + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + app = FastAPI() + e = create_engine("postgresql://localhost") + Session = sessionmaker(bind=e) + + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +This follows advices found in :ref:`session_faq_whentocreate` from SQLAlchemy - linking +the **session scope** to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is ``kill -9``. + +What just happened is called a `resource starvation +`__. While the first 10 +requests were waiting for the async work (``sleep(0.1)``), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default ``max_overflow=10``) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition. + +The root cause of such starvation is making asynchronous calls in a **transaction +scope** created implicitly by the default ``autocommit=False``. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in **transaction scopes** by explicitly ending them:: + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + session.rollback() # return the connection to avoid starvation + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +Because of the implicit nature of typical ORMs, it's very hard to identify all such +**transaction scopes** and make sure there's no ``await`` in them - you may have started +an implicit transaction by simply accessing a property like ``current_user.name``. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this. + +What about thread pool? + +The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool. + +This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery_. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design. + +In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs. + +.. _Celery: http://www.celeryproject.org/ + + +Asynchronous ORM Done Right +--------------------------- + +I would describe a proper asynchronous ORM as follows: + + +Don't Starve. +^^^^^^^^^^^^^ + +The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything ``async``. + +In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:: + + @app.get("/") + async def read_root(): + async with db.acquire() as conn: # } + async with conn.begin(): # } These two won't block the main thread + now = await db.scalar("SELECT now()") + await asyncio.sleep(0.1) # do some async work + return str(now) + +Even though this code won't cause any resource starvation, using ``await`` within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages: + +1. As the database connection pool is usually much smaller than the asynchronous server + concurrency, this kind of code caps the concurrency down to the DB pool level. +2. Taking a database connection from the pool for nothing is a waste of resource, + especially when the database pool is the shortest stave. +3. Database transactions should be kept short as much as possible. Because long-hanging + transactions may keep certain database locks, leading to performance issues or even + triggering a chain reaction of deadlocks. + + +Be explicit. +^^^^^^^^^^^^ + +With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an ``await`` or ``async with``, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following `the pattern of Twisted +`__, asyncio is already forcing +explicit ``await`` for any asynchronous operations. + +Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example: + +* We could design the ORM model instances to be stateless, so that the users don't have + to learn and worry about maintaining the state of the instances. +* There shouldn't be any "buffered operations" which users could "flush" with a single + statement once for all. +* The user should just give direct one-off commands and the ORM executes them right away. +* Also I think the convenience tooling should be well-balanced, the user doesn't have to + guess or remember what an API means - for example, there're more than one ways to load + a many-to-one relationship, I'd prefer to write the query by myself rather than trying + to remember what "join_without_n_plus_1()" means. + + +Be productive. +^^^^^^^^^^^^^^ + +Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two? + +I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design. + +Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again. diff --git a/docs/zh/1.0/_sources/how-to.rst.txt b/docs/zh/1.0/_sources/how-to.rst.txt new file mode 100644 index 0000000..00a13df --- /dev/null +++ b/docs/zh/1.0/_sources/how-to.rst.txt @@ -0,0 +1,7 @@ +How-to Guides +============= + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/zh/1.0/_sources/how-to/alembic.rst.txt b/docs/zh/1.0/_sources/how-to/alembic.rst.txt new file mode 100644 index 0000000..aa60714 --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/alembic.rst.txt @@ -0,0 +1,135 @@ +Use Alembic +=========== + +Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO. + +To add migrations to project first of all, add alembic as dependency: + +.. code-block:: console + + $ pip install --user alembic + +When you need to set up alembic for your project. + +Prepare sample project. We will have a structure: + + +.. code-block:: console + + alembic_sample/ + my_app/ + models.py + +Inside ``models.py`` define simple DB Model with GINO: + +.. code-block:: python + + from gino import Gino + + db = Gino() + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + +Set up Alembic +^^^^^^^^^^^^^^ + +This will need to be done only once. Go to the main folder of your project +``alembic_sample`` and run: + +.. code-block:: console + + $ alembic init alembic + + +Alembic will create a bunch of files and folders in your project directory. One of them +will be ``alembic.ini``. Open ``alembic.ini`` (you can find it in the main project +folder ``alembic_sample``). Now change property ``sqlalchemy.url =`` with your DB +credentials. Like this: + +.. code-block:: ini + + sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}} + + +Next go to folder ``alembic/`` and open ``env.py`` file. Inside the ``env.py`` file you +need to import the ``db`` object. In our case ``db`` object is ``db`` from ``models`` +modules. This is a variable that links to your ``Gino()`` instance. + +Inside ``alembic/env.py``:: + + from main_app.models import db + + +And change ``target_metadata =`` to:: + + target_metadata = db + +That’s it. We finished setting up Alembic for a project. + +.. note:: + + All ``alembic`` commands must be run always from the folder that contains the + ``alembic.ini`` file. + + +Create first migration revision +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema. + +.. code-block:: console + + $ alembic revision -m "first migration" --autogenerate --head head + +If you have any problems relative to package imports similar to this example: + +.. code-block:: console + + File "alembic/env.py", line 7, in + from main_app.models import db + ModuleNotFoundError: No module named 'main_app' + +Either install your project locally with ``pip install -e .``, ``poetry install`` or +``python setup.py develop``, or add you package to PYTHONPATH, like this: + +.. code-block:: console + + $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample + +After the successful run of ``alembic revision`` in folder ``alembic/versions`` you will +see a file with new migration. + + +Apply migration on DB +^^^^^^^^^^^^^^^^^^^^^ + +Now time to apply migration to DB. It will create tables based on you DB Models. + +.. code-block:: console + + $ alembic upgrade head + +Great. Now you apply your first migration. Congratulations! + +Next time, when you will make any changes in DB models just do: + +.. code-block:: console + + $ alembic revision -m "your migration description" --autogenerate --head head + +And + +.. code-block:: console + + alembic upgrade head + + +Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org diff --git a/docs/zh/1.0/_sources/how-to/contributing.rst.txt b/docs/zh/1.0/_sources/how-to/contributing.rst.txt new file mode 100644 index 0000000..cbab989 --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/contributing.rst.txt @@ -0,0 +1,152 @@ +.. highlight:: console + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/python-gino/gino/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `gino` for local development. + +1. Fork the `gino` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/gino.git + +3. Create a branch for local development:: + + $ cd gino/ + $ git checkout -b name-of-your-bugfix-or-feature + +Now you can make your changes locally. + +4. Create virtual environment. Example for virtualenvwrapper:: + + $ mkvirtualenv gino + +5. Activate the environment and install requirements:: + + $ pip install -r requirements_dev.txt + +6. When you're done making changes, check that your changes pass syntax checks:: + + $ flake8 gino tests + +7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):: + + $ pytest tests + $ tox + +8. For docs run:: + + $ make docs + +It will build and open up docs in your browser. + +9. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +10. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check + https://github.com/python-gino/gino/actions?query=event%3Apull_request + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test -svx tests.test_gino + +By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:: + + CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino'; + CREATE DATABASE gino WITH OWNER = gino; + +Then run the tests like so:: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino + $ py.test + +Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):: + + $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem + $ openssl rsa -in privkey.pem -passin pass:abcd -out server.key + $ openssl req -x509 -in server.req -text -key server.key -out server.crt + $ chmod 600 server.key + $ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + +Terminal 2 (client):: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433 + $ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'" + $ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;" + $ pytest tests/test_aiohttp.py diff --git a/docs/zh/1.0/_sources/how-to/crud.rst.txt b/docs/zh/1.0/_sources/how-to/crud.rst.txt new file mode 100644 index 0000000..dd23336 --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/crud.rst.txt @@ -0,0 +1,5 @@ +==== +CRUD +==== + +**THIS IS A WIP** diff --git a/docs/zh/1.0/_sources/how-to/faq.rst.txt b/docs/zh/1.0/_sources/how-to/faq.rst.txt new file mode 100644 index 0000000..73c340a --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/faq.rst.txt @@ -0,0 +1,389 @@ +Frequently Asked Questions +========================== + +SQLAlchemy 1.4 supports asyncio, what will GINO be? +--------------------------------------------------- + +Starting from 1.4, SQLAlchemy will `support asyncio +`__. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +`greenlet `__. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed. + +Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features: + +* Contextual Connections (see :doc:`../explanation/engine`) +* SQLAlchemy Core-based CRUD models +* The GINO Loader system +* Async MySQL support +* Typing support :sup:`NEW` +* `Trio `__ support :sup:`NEW` +* Execution performance :sup:`NEW` + +As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized. + + +ORM or not ORM? +--------------- + +GINO does perform the Object-Relational Mapping work under the +`Data Mapper Pattern `_, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely `non-ORM way +`__. + + +Can I use features of SQLAlchemy ORM? +------------------------------------- + +SQLAlchemy has `two parts `__: + +* SQLAlchemy Core +* SQLAlchemy ORM + +GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO. + + +How to join? +------------ + +GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know `how to write join in +SQLAlchemy `_. +Especially, `Ádám `_ made some amazing upgrades in +GINO `#113 `_ to make join easier, so +that you can use model classes directly as if they are tables in joining:: + + results = await User.join(Book).select().gino.all() + + +How to connect to database through SSL? +--------------------------------------- + +It depends on the dialect and database driver. For asyncpg, keyword arguments +on :func:`asyncpg.connect() ` are directly +available on :func:`~gino.create_engine` or :meth:`db.set_bind() +`. Therefore, enabling SSL is rather easy:: + + engine = await gino.create_engine(..., ssl=True) + + +What is aiocontextvars and what does it do? +------------------------------------------- + +It is a partial backport of the new built-in module `contextvars +`_ introduced in Python +3.7. In Python 3.5 and 3.6, ``aiocontextvars`` patches ``loop.create_task()`` +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2 + +If you are using Python 3.7, then ``aiocontextvars`` does nothing at all. + +.. note:: + + This answer is for GINO 0.8 and later, please check earlier versions of + this documentation if you are using GINO 0.7. + + +How to define relationships? +---------------------------- + +GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see :doc:`loaders` for more information. + + +How to define index with multiple columns? +------------------------------------------ + +:: + + class User(db.Model): + __tablename__ = 'users' + + first_name = db.Column(db.Unicode()) + last_name = db.Column(db.Unicode()) + + _name_idx = db.Index('index_on_name', 'first_name', 'last_name') + +The ``_name_idx`` is not used. + + +Is there a django admin interface for GINO? +------------------------------------------- + +Not quite yet, please follow `this discussion +`__. + + +How to use multiple databases for different users on the fly? +------------------------------------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. + +In order to use multiple databases, you would need multiple +:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with +lazy engine creation:: + + from asyncio import Future + from contextvars import ContextVar + + from fastapi import FastAPI, Request + from gino import create_engine + from gino.ext.starlette import Gino + + engines = {} + dbname = ContextVar("dbname") + + + class ContextualGino(Gino): + @property + def bind(self): + e = engines.get(dbname.get("")) + if e and e.done(): + return e.result() + else: + return self._bind + + @bind.setter + def bind(self, val): + self._bind = val + + + app = FastAPI() + db = ContextualGino(app) + + + @app.middleware("http") + async def lazy_engines(request: Request, call_next): + name = request.query_params.get("db", "postgres") + fut = engines.get(name) + if fut is None: + fut = engines[name] = Future() + try: + engine = await create_engine("postgresql://localhost/" + name) + except Exception as e: + fut.set_exception(e) + del engines[name] + raise + else: + fut.set_result(engine) + await fut + dbname.set(name) + return await call_next(request) + + + @app.get("/") + async def get(): + return dict(dbname=await db.scalar("SELECT current_database()")) + + +How to load complex query results? +---------------------------------- + +The API doc of :mod:`gino.loader` explains the available loaders, and there're a few +examples in :doc:`loaders` too. + +Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders - +:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Be ware of the :class:`tuple` in ``.gino.load((...))``. + + + +How to do bulk or batch insert / update? +----------------------------------------- + +For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:: + + new_names = ["Austin", "Ali", "Jeff", "Marissa"] + +To quickly insert the names in one query, first construct a dict with the +``{"model_key": "value"}`` format:: + + new_names_dict = [dict(name=new_name) for new_name in new_names] + >> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}] + +Finally, run an insert statement on the model:: + + await User.insert().gino.all(new_names_dict) + + +How to print the executed SQL? +------------------------------ + +GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``. +(Or ``db.set_bind(..., echo=True)``) Please see also `here +`__. + +If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`. + + +How to run ``EXISTS`` SQL? +-------------------------- + +:: + + await db.scalar(db.exists().where(User.email == email).select()) + + +How to work with Alembic? +------------------------- + +The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the +key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py`` +will do. See :doc:`alembic` for more details. + + +How to join the same table twice? +--------------------------------- + +This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +:func:`~gino.crud.CRUDModel.alias`, for example:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.ForeignKey("users.id")) + + Parent = User.alias() + query = User.outerjoin(Parent, User.parent_id == Parent.id).select() + users = await query.gino.load(User.load(parent=Parent)).all() + + +.. _raw-sql: + +How to execute raw SQL with parameters? +--------------------------------------- + +Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments:: + + query = db.text('SELECT * FROM users WHERE id = :id_val') + row = await db.first(query, id_val=1) + +You may even load the rows into model instances:: + + query = query.execution_options(loader=User) + user = await db.first(query, id_val=1) + + +Gino engine is not initialized? +------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before +this, you'll see this error: + +.. code-block:: text + + gino.exceptions.UninitializedError: Gino engine is not initialized. + +You could use either: + +* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the + bind on the :class:`~gino.api.Gino` instance. +* Use one of the Web framework extensions to set the bind for you in usually the server + start-up hook. +* Use explicit ``bind`` for each execution, for example:: + + engine = await create_engine("...") + # ... + user = await User.get(request.user_id, bind=engine) + + +How can I do SQL xxxx in GINO? +------------------------------ + +GINO uses `SQLAlchemy Core `__ queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column +attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column` +instances, you can use them in building your SQLAlchemy Core queries. + +Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above. diff --git a/docs/zh/1.0/_sources/how-to/json-props.rst.txt b/docs/zh/1.0/_sources/how-to/json-props.rst.txt new file mode 100644 index 0000000..45cf95d --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/json-props.rst.txt @@ -0,0 +1,157 @@ +JSON Property +============= + +GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields. + +Quick Start +----------- + +:: + + from gino import Gino + from sqlalchemy.dialects.postgresql import JSONB + + db = Gino() + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + birthday = db.DateTimeProperty() + +The ``age`` and ``birthday`` are JSON properties stored in the ``profile`` column. You +may use them the same way as a normal GINO model field:: + + u = await User.create(name="daisy", age=18) + print(u.name, u.age) # daisy 18 + +.. note:: + + ``profile`` is the default column name for all JSON properties in a model. If you + need a different column name for some JSON properties, you'll need to specify + explicitly:: + + audit_profile = db.Column(JSON, nullable=False, server_default="{}") + + access_log = db.ArrayProperty(prop_name="audit_profile") + abnormal_detected = db.BooleanProperty(prop_name="audit_profile") + +Using JSON properties in queries is supported:: + + await User.query.where(User.age > 16).gino.all() + +This is simply translated into a native JSON query like this: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16) + +Datetime type is very much the same:: + + from datetime import datetime + + await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all() + +And the generated SQL: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2 + -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0)) + +Here's a list of all the supported JSON properties: + ++----------------------------+-----------------------------+-------------+---------------+ +| JSON Property | Python type | JSON type | Database Type | ++============================+=============================+=============+===============+ +| :class:`.StringProperty` | :class:`str` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.IntegerProperty` | :class:`int` | ``number`` | ``int`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.BooleanProperty` | :class:`bool` | ``boolean`` | ``boolean`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.DateTimeProperty` | :class:`~datetime.datetime` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ObjectProperty` | :class:`dict` | ``object`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ArrayProperty` | :class:`list` | ``array`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ + + +Hooks +----- + +JSON property provides 2 instance-level hooks to customize the data:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @age.before_set + def age(self, val): + return val - 1 + + @age.after_get + def age(self, val): + return val + 1 + + u = await User.create(name="daisy", age=18) + print(u.name, u.profile, u.age) # daisy {'age': 17} 18 + +And 1 class-level hook to customize the SQLAlchemy expression of the property:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + height = db.JSONProperty() + + @height.expression + def height(cls, exp): + return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT) + + +Create Index on JSON Properties +------------------------------- + +We'll need to use :meth:`~gino.declarative.declared_attr` to wait until the model class +is initialized. The rest is very much the same as defining a usual index:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @db.declared_attr + def age_idx(cls): + return db.Index("age_idx", cls.age) + +This will lead to the SQL below executed if you run ``db.gino.create_all()``: + +.. code-block:: plpgsql + + CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER)); + +.. warning:: + + Alembic doesn't support auto-generating revisions for functional indexes yet. You'll + need to manually edit the revision. Please follow `this issue + `__ for updates. diff --git a/docs/zh/1.0/_sources/how-to/loaders.rst.txt b/docs/zh/1.0/_sources/how-to/loaders.rst.txt new file mode 100644 index 0000000..6d82937 --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/loaders.rst.txt @@ -0,0 +1,461 @@ +======================== +Loaders and Relationship +======================== + +Loaders are used to load database row results into objects. + +GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined. + + +Model Loader +------------ + +The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:: + + query = db.select([User]) + rows = await query.gino.all() + +In order to load rows into ``User`` objects, you can provide an execution +option ``loader`` with a new :class:`~gino.loader.ModelLoader` instance:: + + from gino.loader import ModelLoader + + query = db.select([User]) + query = query.execution_options(loader=ModelLoader(User)) + users = await query.gino.all() + +The :class:`~gino.loader.ModelLoader` would then load each database row into a +``User`` object. As this is frequently used, GINO made it a shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User.load()) + users = await query.gino.all() + +And another shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User) + users = await query.gino.all() + +.. tip:: + + ``User`` as ``loader`` is transformed into ``ModelLoader(User)`` by + :meth:`Loader.get() `, explained later in "Loader + Expression". + +And again:: + + query = db.select([User]) + users = await query.gino.load(User).all() + +This is identical to the normal CRUD query:: + + users = await User.query.gino.all() + + +Loader Expression +----------------- + +So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than :class:`~gino.loader.ModelLoader`, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders. + +Here is an example using all loaders at once:: + + uid, user, sep, cols = await db.select([User]).gino.load( + ( + User.id, + User, + '|', + lambda row, ctx: len(row), + ) + ).first() + +Let's check this piece by piece. Overall, the argument of +:meth:`~gino.api.GinoExecutor.load` is a tuple. This is interpreted into a +:class:`~gino.loader.TupleLoader`, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a :class:`~gino.loader.TupleLoader` is a tuple. + +:class:`~sqlalchemy.schema.Column` in Loader Expressions are interpreted as +:class:`~gino.loader.ColumnLoader`. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, :class:`~gino.loader.ColumnLoader` uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use :class:`~gino.loader.ColumnLoader`, +you'll have to declare columns for the query:: + + now = db.Column('time', db.DateTime()) + result = await db.first(db.text( + 'SELECT now() AT TIME ZONE \'UTC\'' + ).columns( + now, + ).gino.load( + ('now:', now) + ).query) + print(*result) # now: 2018-04-08 08:23:02.431847 + +Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +:class:`~gino.loader.ModelLoader`. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values. + +.. tip:: + + For a complex loader expression, the same row is given to all loaders, so + it doesn't matter ``User.id`` is already used before the model loader. + +The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +:func:`map`, the return value of the call will be the loaded result. + +At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the ``'|'`` separator, it will show up as the third item +in every result returned by the query. + + +Many-to-One Relationship +------------------------ + +A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + +So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:: + + async for child in Child.load(parent=Parent).gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +As you may have noticed, ``Child.load`` is exactly the shortcut to create +:class:`~gino.loader.ModelLoader` in the very first example. With some +additional keyword arguments, ``Child.load(parent=Parent)`` is still a +:class:`~gino.loader.ModelLoader` for ``Child``, the model loader is at the +same time a **query builder**. It is identical to do this:: + + async for child in Child.load(parent=Parent).query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +The :attr:`~gino.loader.Loader.query` dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The :class:`~gino.loader.Loader` simply forwarded unknown +attributes to its :attr:`~gino.loader.Loader.query`, that's why ``.query`` can +be omitted. + +For :class:`~gino.loader.ModelLoader`, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using :func:`setattr`. For example, ``Parent`` is +interpreted as ``ModelLoader(Parent)`` which loads ``Parent`` instances, and +``Parent`` instances are set as the ``parent`` attribute of the outer ``Child`` +instance. + +.. warning:: + + If multiple children references the same parent, then each child owns a + unique parent instance with identical values. + +.. tip:: + + You don't have to define ``parent`` attribute on ``Child``. But if you do, + you gain the ability to customize how parent is stored or retrieved. For + example, let's store the parent instance as ``_parent``:: + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + _parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + +The query builder works recursively. For :class:`~gino.loader.ModelLoader`, it +uses ``LEFT OUTER JOIN`` to connect the ``FROM`` clauses, in order to achieve +many-to-one scenario. The ``ON`` clause is determined automatically by foreign +keys. You can also customize the ``ON`` clause in case there is no foreign key +(a promise is a promise):: + + loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + async for child in loader.query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +And subloaders can be nested:: + + subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id)) + +By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values. + + +Self Referencing +---------------- + +.. warning:: + + Experimental feature. + +Self referencing is usually used to create a tree-like structure. For example:: + + class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('categories.id')) + +In order to load leaf categories with their parents, an alias is needed:: + + Parent = Category.alias() + +Then the query would be something like this:: + + parents = db.select([Category.parent_id]) + query = Category.load(parent=Parent.on( + Category.parent_id == Parent.id + )).where( + ~Category.id.in_(db.select([Category.alias().parent_id])) + ) + async for c in query.gino.iterate(): + print(f'Leaf: {c.id}, Parent: {c.parent.id}') + +The generated SQL looks like this: + +.. code-block:: SQL + + SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id + FROM categories LEFT OUTER JOIN categories AS categories_1 + ON categories.parent_id = categories_1.id + WHERE categories.id NOT IN ( + SELECT categories_2.parent_id + FROM categories AS categories_2 + ) + + +Other Relationships +------------------- + +GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + @children.setter + def add_child(self, child): + self._children.add(child) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child)).all() + +Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the ``add_child`` setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +``parent.add_child = new_child``. + +Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries. + +GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._child = None + + @property + def child(self): + return self._child + + @child.setter + def child(self, child): + self._child = child + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all() + + +Similarly, you can build many-to-many relationships in the same way:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + def add_child(self, child): + self._children.add(child) + child._parents.add(self) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._parents = set() + + @property + def parents(self): + return self._parents + + + class ParentXChild(db.Model): + __tablename__ = 'parents_x_children' + + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + child_id = db.Column(db.Integer, db.ForeignKey('children.id')) + + + query = Parent.outerjoin(ParentXChild).outerjoin(Child).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all() + +Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ``ParentXChild`` instances. + + +Advanced Usage of Loaders +------------------------- + +You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For `example +`_, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Using alias to get ID-ascending pairs from the same table:: + + ua1 = User.alias() + ua2 = User.alias() + join_query = select([ua1, ua2]).where(ua1.id < ua2.id) + loader = ua1.load('id'), ua2.load('id') + result = await join_query.gino.load(loader).all() + print(result) # e.g. [(1, 2), (1, 3), (2, 3)] + +Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future. diff --git a/docs/zh/1.0/_sources/how-to/pool.rst.txt b/docs/zh/1.0/_sources/how-to/pool.rst.txt new file mode 100644 index 0000000..4574ab1 --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/pool.rst.txt @@ -0,0 +1,27 @@ +=============== +Connection Pool +=============== + +Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +:class:`~gino.dialects.asyncpg.NullPool`), and users can define their own pools. +The base class should be :class:`~gino.dialects.base.Pool`. + +To use non-default pools in raw GINO:: + + from gino.dialects.asyncpg import NullPool + create_engine('postgresql://...', pool_class=NullPool) + +To use non-default pools in extensions (taking Sanic as an example):: + + from gino.dialects.asyncpg import NullPool + from gino.ext.sanic import Gino + + app = sanic.Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_KWARGS = dict( + pool_class=NullPool, + ) + db = Gino() + db.init_app(app) diff --git a/docs/zh/1.0/_sources/how-to/schema.rst.txt b/docs/zh/1.0/_sources/how-to/schema.rst.txt new file mode 100644 index 0000000..c57eaaa --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/schema.rst.txt @@ -0,0 +1,232 @@ +================== +Schema Declaration +================== + +There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy :class:`~sqlalchemy.schema.Table`. + + +GINO Engine +----------- + +This is the minimized way to use GINO - using only +:class:`~gino.engine.GinoEngine` (and :class:`~gino.engine.GinoConnection` +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two. + +For example, the table declaration is the same as SQLAlchemy core `tutorial +`_:: + + from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey + + metadata = MetaData() + + users = Table( + 'users', metadata, + + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + ) + + addresses = Table( + 'addresses', metadata, + + Column('id', Integer, primary_key=True), + Column('user_id', None, ForeignKey('users.id')), + Column('email_address', String, nullable=False) + ) + +.. note:: + + When using GINO Engine only, it is usually your own business to create the + tables with either :meth:`~sqlalchemy.schema.MetaData.create_all` on a + normal non-async SQLAlchemy engine, or using Alembic. However it is still + possible to be done with GINO if it had to:: + + import gino + from gino.schema import GinoSchemaVisitor + + async def main(): + engine = await gino.create_engine('postgresql://...') + await GinoSchemaVisitor(metadata).create_all(engine) + +Then, construct queries, in SQLAlchemy core too:: + + ins = users.insert().values(name='jack', fullname='Jack Jones') + +So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:: + + async def main(): + engine = await gino.create_engine('postgresql://localhost/gino') + conn = await engine.acquire() + await conn.status(ins) + print(await conn.all(users.select())) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Here :func:`~gino.create_engine` creates a :class:`~gino.engine.GinoEngine`, +then :meth:`~gino.engine.GinoEngine.acquire` checks out a +:class:`~gino.engine.GinoConnection`, and +:meth:`~gino.engine.GinoConnection.status` executes the insert and returns the +status text. This works similarly as SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execute` - they take the same parameters +but return a bit differently. There are also other similar query APIs: + +* :meth:`~gino.engine.GinoConnection.all` returns a list of + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.first` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.one` returns one + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.one_or_none` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.scalar` returns a single value, or + ``None`` +* :meth:`~gino.engine.GinoConnection.iterate` returns an asynchronous iterator + which yields :class:`~sqlalchemy.engine.RowProxy` + +Please go to their API for more information. + + +GINO Core +--------- + +In previous scenario, :class:`~gino.engine.GinoEngine` must not be set to +:attr:`metadata.bind ` because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of :class:`~sqlalchemy.schema.MetaData` as :class:`~gino.api.Gino`, +usually instantiated globally under the name of ``db``. It can be used as a +normal :class:`~sqlalchemy.schema.MetaData` still offering some conveniences: + +* It delegates most public types you can access on ``sqlalchemy`` +* It works with both normal SQLAlchemy engine and asynchronous GINO engine +* It exposes all query APIs on :class:`~gino.engine.GinoConnection` level +* It injects two ``gino`` extensions on SQLAlchemy query clauses and schema + items, allowing short inline execution like ``users.select().gino.all()`` +* It is also the entry for the third scenario, see later + +Then we can achieve previous scenario with less code like this:: + + from gino import Gino + + db = Gino() + + users = db.Table( + 'users', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('name', db.String), + db.Column('fullname', db.String), + ) + + addresses = db.Table( + 'addresses', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('user_id', None, db.ForeignKey('users.id')), + db.Column('email_address', db.String, nullable=False) + ) + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await users.insert().values( + name='jack', + fullname='Jack Jones', + ).gino.status() + print(await users.select().gino.all()) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but ``sqlalchemy`` seems +never imported. This is useful when ORM is unwanted. + +.. tip:: + + `asyncpgsa `_ does the same thing, + but in a conceptually reversed way - instead of having asyncpg work for + SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that + way too because GINO is inspired by asyncpgsa). Either way works fine, it's + just a matter of taste of whose API style to use, SQLAlchemy or asyncpg. + + +GINO ORM +-------- + +If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:: + + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + fullname = db.Column(db.String) + + + class Address(db.Model): + __tablename__ = 'addresses' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(None, db.ForeignKey('users.id')) + email_address = db.Column(db.String, nullable=False) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await User.create(name='jack', fullname='Jack Jones') + print(await User.query.gino.all()) + # Outputs: [] + +.. important:: + + The ``__tablename__`` is a mandatory field to define a concrete model. + +As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in ``db``. The ``class`` style is just +more declarative. Instead of ``users.c.name``, you can now access the column by +``User.name``. The implicitly created :class:`~sqlalchemy.schema.Table` is +available at ``User.__table__`` and ``Address.__table__``. You can use anything +that works in GINO core here. + +.. note:: + + Column names can be different as a class property and database column. + For example, name can be declared as + ``nickname = db.Column('name', db.Unicode(), default='noname')``. In this + example, ``User.nickname`` is used to access the column, while in database, + the column name is ``name``. + + What's worth mentioning is where raw SQL statements are used, or + ``TableClause`` is involved, like ``User.insert()``, the original name is + required to be used, because in this case, GINO has no knowledge about the + mappings. + +.. tip:: + + ``db.Model`` is a dynamically created parent class for your models. It is + associated with the ``db`` on initialization, therefore the table is put in + the very ``db`` when you declare your model class. + +Things become different when it comes to CRUD. You can use model level methods +to directly :meth:`~gino.crud.CRUDModel.create` a model instance, instead of +inserting a new row. Or :meth:`~gino.crud.CRUDModel.delete` a model instance +without needing to specify the where clause manually. Query returns model +instances instead of :class:`~sqlalchemy.engine.RowProxy`, and row values are +directly available as attributes on model instances. See also: +:doc:`/how-to/crud`. + +After all, :class:`~gino.engine.GinoEngine` is always in use. Next let's dig +more into it. diff --git a/docs/zh/1.0/_sources/how-to/transaction.rst.txt b/docs/zh/1.0/_sources/how-to/transaction.rst.txt new file mode 100644 index 0000000..558a5ae --- /dev/null +++ b/docs/zh/1.0/_sources/how-to/transaction.rst.txt @@ -0,0 +1,116 @@ +=========== +Transaction +=========== + +It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an ``await`` will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it. + + +Basic usage +----------- + +Transactions belong to :class:`~gino.engine.GinoConnection`. The most common +way to use transactions is through an ``async with`` statement:: + + async with connection.transaction() as tx: + await connection.status('INSERT INTO mytable VALUES(1, 2, 3)') + +This guarantees a transaction is opened when entering the ``async with`` block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at :attr:`~gino.transaction.GinoTransaction.raw_transaction`, but in +most cases you don't need to touch it. + +GINO provides two convenient shortcuts to end the transaction early: + +* :meth:`tx.raise_commit() ` +* :meth:`tx.raise_rollback() ` + +They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the ``async with`` block after +:meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` is skipped. The +internal exception is inherited from :exc:`BaseException` so that normal ``try +... except Exception`` block can't trap it. This exception stops propagating at +the end of ``async with`` block, so you don't need to worry about handling it. + +Transactions can also be started on a :class:`~gino.engine.GinoEngine`:: + + async with engine.transaction() as tx: + await engine.status('INSERT INTO mytable VALUES(1, 2, 3)') + +Here a :class:`~gino.engine.GinoConnection` is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The :class:`~gino.engine.GinoConnection` instance is accessible at +:attr:`tx.connection `. Other than +that, everything else is the same. + +.. important:: + + The implicit connection is by default borrowed with ``reuse=True``. That + means using :meth:`~gino.engine.GinoEngine.transaction` of + :class:`~gino.engine.GinoEngine` within a connection context is the same as + calling :meth:`~gino.engine.GinoConnection.transaction` of the current + connection without having to reference it, no separate connection shall be + created. + +Similarly, if your :class:`~gino.api.Gino` instance has a bind, you may also do +the same on it:: + + async with db.transaction() as tx: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + + +Nested Transactions +------------------- + +Transactions can be nested, nested transaction will create a `savepoint +`_ as for +now on asyncpg. A similar example from asyncpg doc:: + + async with connection.transaction() as tx1: + await connection.status('CREATE TABLE mytab (a int)') + + # Create a nested transaction: + async with connection.transaction() as tx2: + await connection.status('INSERT INTO mytab (a) VALUES (1), (2)') + # Rollback the nested transaction: + tx2.raise_rollback() + + # Because the nested transaction was rolled back, there + # will be nothing in `mytab`. + assert await connection.all('SELECT a FROM mytab') == [] + +As you can see, the :meth:`~gino.transaction.GinoTransaction.raise_rollback` +breaks only the ``async with`` block of the specified ``tx2``, the outer +transaction ``tx1`` just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate. + +Because of the default reusing behavior, transactions on engine or ``db`` +follows the same nesting rules. Please see +:class:`~gino.transactions.GinoTransaction` for more information. + + +Manual Control +-------------- + +Other than using ``async with``, you can also manually control the +transaction:: + + tx = await connection.transaction() + try: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + await tx.commit() + except Exception: + await tx.rollback() + raise + +You can't use :meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` here, similarly it is +prohibited to use :meth:`~gino.transaction.GinoTransaction.commit` and +:meth:`~gino.transaction.GinoTransaction.rollback` in an ``async with`` block. diff --git a/docs/zh/1.0/_sources/index.rst.txt b/docs/zh/1.0/_sources/index.rst.txt new file mode 100644 index 0000000..1512377 --- /dev/null +++ b/docs/zh/1.0/_sources/index.rst.txt @@ -0,0 +1,127 @@ +Welcome to GINO's documentation! +================================ + +.. image:: https://img.shields.io/pypi/v/gino?logo=python&logoColor=white&color=3E6CDE&style=flat-square + :alt: PyPI Release Version + :target: https://pypi.python.org/pypi/gino + +.. image:: https://img.shields.io/github/workflow/status/python-gino/gino/test?label=test&logo=github&color=3E6CDE&style=flat-square + :alt: GitHub Workflow Status for tests + :target: https://github.com/python-gino/gino/actions?query=workflow%3Atest + +.. image:: https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy&color=3E6CDE&style=flat-square + :alt: Codacy coverage + :target: https://app.codacy.com/gh/python-gino/gino/dashboard + +.. image:: https://img.shields.io/badge/Dependabot-active-brightgreen?logo=dependabot&color=3E6CDE&style=flat-square + :target: https://app.dependabot.com/accounts/python-gino/projects/129260 + :alt: Dependabot + + +GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy_ core for Python asyncio_. Now (early 2020) GINO supports only one +dialect asyncpg_. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _asyncpg: https://github.com/MagicStack/asyncpg + + +.. cssclass:: boxed-nav + +* .. image:: images/tutorials.svg + + :doc:`tutorials` + + Lessons for the newcomer to get started + +* .. image:: images/how-to.svg + + :doc:`how-to` + + Solve specific problems by steps + +* .. image:: images/explanation.svg + + :doc:`explanation` + + Explains the background and context + +* .. image:: images/reference.svg + + :doc:`reference` + + Describes the software as it is + + +Useful Links +------------ + +.. cssclass:: boxed-nav + +* .. image:: images/github.svg + + `Source Code `_ + + https://github.com/python-gino/gino + +* .. image:: images/community.svg + + `Community `_ + + https://gitter.im/python-gino/Lobby + +* .. image:: images/open-source.svg + + `BSD license `_ + + GINO is free software + +* .. image:: images/python.svg + + `Download `_ + + Download GINO from PyPI + + +.. cssclass:: divio + +Sections by `Divio `_. + +.. toctree:: + :caption: Tutorials + :maxdepth: 1 + :glob: + :hidden: + + tutorials + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* + +.. toctree:: + :caption: How-to Guides + :maxdepth: 1 + :glob: + :hidden: + + how-to + how-to/* + +.. toctree:: + :caption: Explanation + :maxdepth: 1 + :glob: + :hidden: + + explanation + explanation/* + +.. toctree:: + :caption: Reference + :maxdepth: 1 + :glob: + :hidden: + + reference + reference/* diff --git a/docs/zh/1.0/_sources/reference.rst.txt b/docs/zh/1.0/_sources/reference.rst.txt new file mode 100644 index 0000000..977912b --- /dev/null +++ b/docs/zh/1.0/_sources/reference.rst.txt @@ -0,0 +1,7 @@ +Reference +========= + +.. toctree:: + :glob: + + reference/* diff --git a/docs/zh/1.0/_sources/reference/api.rst.txt b/docs/zh/1.0/_sources/reference/api.rst.txt new file mode 100644 index 0000000..23fc548 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api.rst.txt @@ -0,0 +1,6 @@ +API Reference +============= + +.. toctree:: + + api/gino diff --git a/docs/zh/1.0/_sources/reference/api/gino.aiocontextvars.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.aiocontextvars.rst.txt new file mode 100644 index 0000000..4728456 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.aiocontextvars.rst.txt @@ -0,0 +1,7 @@ +gino.aiocontextvars module +========================== + +.. automodule:: gino.aiocontextvars + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.api.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.api.rst.txt new file mode 100644 index 0000000..8825a74 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.api.rst.txt @@ -0,0 +1,7 @@ +gino.api module +=============== + +.. automodule:: gino.api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.crud.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.crud.rst.txt new file mode 100644 index 0000000..9683c99 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.crud.rst.txt @@ -0,0 +1,7 @@ +gino.crud module +================ + +.. automodule:: gino.crud + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.declarative.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.declarative.rst.txt new file mode 100644 index 0000000..8ec0bdd --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.declarative.rst.txt @@ -0,0 +1,7 @@ +gino.declarative module +======================= + +.. automodule:: gino.declarative + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.dialects.asyncpg.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.dialects.asyncpg.rst.txt new file mode 100644 index 0000000..9423016 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.dialects.asyncpg.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.asyncpg module +============================ + +.. automodule:: gino.dialects.asyncpg + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.dialects.base.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.dialects.base.rst.txt new file mode 100644 index 0000000..c512657 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.dialects.base.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.base module +========================= + +.. automodule:: gino.dialects.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.dialects.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.dialects.rst.txt new file mode 100644 index 0000000..8e9bc19 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.dialects.rst.txt @@ -0,0 +1,19 @@ +gino.dialects package +===================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects.asyncpg + gino.dialects.base + +Module contents +--------------- + +.. automodule:: gino.dialects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.engine.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.engine.rst.txt new file mode 100644 index 0000000..0075c29 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.engine.rst.txt @@ -0,0 +1,7 @@ +gino.engine module +================== + +.. automodule:: gino.engine + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.exceptions.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.exceptions.rst.txt new file mode 100644 index 0000000..d3659da --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.exceptions.rst.txt @@ -0,0 +1,7 @@ +gino.exceptions module +====================== + +.. automodule:: gino.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.ext.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.ext.rst.txt new file mode 100644 index 0000000..fe56088 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.ext.rst.txt @@ -0,0 +1,10 @@ +gino.ext package +================ + +Module contents +--------------- + +.. automodule:: gino.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.json_support.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.json_support.rst.txt new file mode 100644 index 0000000..5a7c069 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.json_support.rst.txt @@ -0,0 +1,7 @@ +gino.json\_support module +========================= + +.. automodule:: gino.json_support + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.loader.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.loader.rst.txt new file mode 100644 index 0000000..06ce463 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.loader.rst.txt @@ -0,0 +1,7 @@ +gino.loader module +================== + +.. automodule:: gino.loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.rst.txt new file mode 100644 index 0000000..3207440 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.rst.txt @@ -0,0 +1,37 @@ +gino package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects + gino.ext + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.aiocontextvars + gino.api + gino.crud + gino.declarative + gino.engine + gino.exceptions + gino.json_support + gino.loader + gino.schema + gino.strategies + gino.transaction + +Module contents +--------------- + +.. automodule:: gino + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.schema.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.schema.rst.txt new file mode 100644 index 0000000..36fbb62 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.schema.rst.txt @@ -0,0 +1,7 @@ +gino.schema module +================== + +.. automodule:: gino.schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.strategies.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.strategies.rst.txt new file mode 100644 index 0000000..01de55b --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.strategies.rst.txt @@ -0,0 +1,7 @@ +gino.strategies module +====================== + +.. automodule:: gino.strategies + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/api/gino.transaction.rst.txt b/docs/zh/1.0/_sources/reference/api/gino.transaction.rst.txt new file mode 100644 index 0000000..2a28e55 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/api/gino.transaction.rst.txt @@ -0,0 +1,7 @@ +gino.transaction module +======================= + +.. automodule:: gino.transaction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.0/_sources/reference/extensions.rst.txt b/docs/zh/1.0/_sources/reference/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/zh/1.0/_sources/reference/extensions/sanic.rst.txt b/docs/zh/1.0/_sources/reference/extensions/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/extensions/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/zh/1.0/_sources/reference/extensions/starlette.rst.txt b/docs/zh/1.0/_sources/reference/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/zh/1.0/_sources/reference/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/zh/1.0/_sources/reference/extensions/tornado.rst.txt b/docs/zh/1.0/_sources/reference/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/zh/1.0/_sources/reference/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/zh/1.0/_sources/reference/history.rst.txt b/docs/zh/1.0/_sources/reference/history.rst.txt new file mode 100644 index 0000000..d1ee7bb --- /dev/null +++ b/docs/zh/1.0/_sources/reference/history.rst.txt @@ -0,0 +1,609 @@ +======= +History +======= + +GINO 1.0 +-------- + +Migrating to GINO 1.0 +^^^^^^^^^^^^^^^^^^^^^ + +GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras: + ++------------------------+---------------------------------+ +| Extension Module | Installation in GINO 1.0 | ++========================+=================================+ +| ``gino.ext.starlette`` | ``pip install gino[starlette]`` | ++------------------------+---------------------------------+ +| ``gino.ext.aiohttp`` | ``pip install gino[aiohttp]`` | ++------------------------+---------------------------------+ +| ``gino.ext.sanic`` | ``pip install gino[sanic]`` | ++------------------------+---------------------------------+ +| ``gino.ext.tornado`` | ``pip install gino[tornado]`` | ++------------------------+---------------------------------+ +| ``gino.ext.quart`` | ``pip install gino[quart]`` | ++------------------------+---------------------------------+ + +The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed ``gino[starlette]``:: + + from gino.ext.starlette import Gino + +GINO 1.0 switched to `Poetry `__ for package and +dependency management and started to use the +`src layout `__. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process: + +* Source files are now located under ``src`` directory. +* The src dist on PyPI does not include tests, docs and some other files due to + a limitation of Poetry. + +1.0.1 (2020-06-08) +^^^^^^^^^^^^^^^^^^ + +* Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4) +* Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672) +* Multiple JSON property fixes (#661 #662 #695) +* Fixed extension typing issue (#673 #674) +* Fixed model override behavior (#694) +* Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696) + +1.0.0 (2020-04-26) +^^^^^^^^^^^^^^^^^^ + +* Switched to Poetry for package and dependency management. +* [Breaking] Moved built-in extension modules to separate PyPI packages. +* Switched to src layout. +* Switched to black code style. +* Better documentation. +* [Breaking] ``none_as_none()`` is now always enabled. +* Added representation method for engine. +* Protected the URL instance fed to ``set_bind()`` from manipulation. +* Replaced some ``assert`` with ``AssertionError`` (#258 #655) + + +GINO 0.8 +-------- + +This is also version 1.0 release candidate. + +Migrating to GINO 0.8 +^^^^^^^^^^^^^^^^^^^^^ + +1. contextvars +"""""""""""""" + +We introduced aiocontextvars_ 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the ``enable_inherit()`` or +``disable_inherit()`` calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created **after** importing ``gino`` or ``aiocontextvars``, or the patch +won't work correctly. + +There is nothing to worry about in Python 3.7. + +2. none_as_none +""""""""""""""" + +When GINO tries to load a row with all ``NULL`` values into an instance, it +will now by default return ``None`` instead of an instance with all ``None`` +attributes. To recover the default behavior of 0.7, please specify +``none_as_none(False)`` in affected model loader. + +This is especially applicable to relationship sub-loaders - if the sub-loader +found it all ``NULL``, no instance will be set to parent instance. For +example:: + + child = await Child.load(parent=Parent).query.gino.first() + +If ``child.parent_id`` is ``NULL`` in database, then the ``child`` instance +won't be called with any ``setattr(child, 'parent', ...)`` at all. (If you need +``child.parent == None`` in this case, consider setting default value +``parent = None`` in child model.) + +Please note, it is deprecated to disable ``none_as_none``, and disabling will +be removed in GINO 1.0. + +0.8.7 (2020-04-19) +^^^^^^^^^^^^^^^^^^ + +* Improved error handling when attribute names collide (Contributed by Reskov in #637 #638) +* Fixed ``with_bind`` usability in aiohttp extension (#518) + +0.8.6 (2020-02-10) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``JSONPathType`` bind processor for asyncpg (#609) +* Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600) +* Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629) +* Added ``distinct()`` to ``Alias`` (#628) + +0.8.5 (2019-11-19) +^^^^^^^^^^^^^^^^^^ + +* Improved support for ``__tablename__`` in ``declared_attr`` (Contributed by Roald Storm in #592) + +0.8.4 (2019-11-09) +^^^^^^^^^^^^^^^^^^ + +* Better loader support for models in subqueries (#573 #585) +* Allowed ``__tablename__`` to be a ``declared_attr`` (#579 #582) +* Fixed Sanic 19.9.0 compatibility (#569) +* Added one() and one_or_none() (Contributed by Ilaï Deutel in #577) +* Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538) +* Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533) +* Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520) +* Fixed Grammar (Contributed by Simeon J Morgan in #504) + +0.8.3 (2019-06-06) +^^^^^^^^^^^^^^^^^^ + +* Fixed deprecated warnings in asyncpg dialect and aiohttp (#425) +* Customizable db attribute name in aiohttp app instance (#457) +* Added Starlette support (#486) + +0.8.2 (2019-03-07) +^^^^^^^^^^^^^^^^^^ + +* Added exception for unknown JSON properties (#406 #408) +* Supported Quart 0.7 (#411) +* Accepted kwargs for db init in extensions (#407 #427) +* Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440) +* Added NullPool (#437 #441) +* Unpinned dependency versions (#447) +* Added support for SQLAlchemy 1.3 (#433 #451) + +0.8.1 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Alias supported ``Label`` (#365) +* Docs update (#308, 4c59ad, #401 by Pascal van Kooten) +* Version requirement for SQLAlchemy is updated to ``>=1.2`` (#378 #382) +* Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) + * And all other extensions (#395) +* Supported Tornado 5 (#396, also thanks to Vladimir Goncharov) +* Fixed custom JSON/JSONB type support (#402 #403) + +(Most fixes done by Tony Wang) + +0.8.0 (2018-10-16) +^^^^^^^^^^^^^^^^^^ + +* Welcome Tony Wang to the maintenance team (#335) +* Allowed custom column names (#261 #297) +* Allowed column instance in ``model.load()`` (Contributed by Jekel in #323) +* [Breaking] Upgraded to aiocontextvars 0.2.0 (#333) +* Fixed bug that the same empty stack is shared between sub-tasks (#313 #334) +* [Breaking] Made ``none_as_none()`` the default behavior (#351) +* Bug fixes and docs update + + +GINO 0.7 +-------- + +This is also version 1.0 beta 3. + +0.7.7 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Backported fix for custom JSON/JSONB type support (#402 #403) + +0.7.6 (2018-08-26) +^^^^^^^^^^^^^^^^^^ + +* Updated library support (Contributed by Tony Wang in #275 #309) +* Added ``none_as_none()`` (#281 #282) +* Added ``ARRAY`` alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289) +* Added ``Model.lookup()`` to prevent updating whole table without primary key (#287 #288) +* Added ``DB_ECHO`` in extension options (Contributed by Mykyta Holubakha in #298) +* Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305) +* Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304) +* Fixed to raise ``UninitializedError`` if bind is ``None`` (Contributed by Tony Wang in #307 #310) + +0.7.5 (2018-07-26) +^^^^^^^^^^^^^^^^^^ + +* Added friendly error message when using abstract models by mistake (#224) +* Supported Python 3.7 (Contributed by Tony Wang in #265) +* Updated documentation +* Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280) + +0.7.4 (2018-06-10) +^^^^^^^^^^^^^^^^^^ + +* Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228) +* Added Quart support (#213) +* Fixed Tornado options parsing (#231) +* Improved coding style and test coverage + +0.7.3 (2018-05-19) +^^^^^^^^^^^^^^^^^^ + +* Fix for failing binary type (#225) + +0.7.2 (2018-05-15) +^^^^^^^^^^^^^^^^^^ + +* Added prepared statement support (#14) +* Added dsn in extension config (Contributed by Yurii Shtrikker in #215) + +0.7.1 (2018-05-03) +^^^^^^^^^^^^^^^^^^ + +* Added support for inline model constraints (Contributed by Kinware in #198) +* Added docs and tests for using SSL (#202) +* Added ``declared_attr`` (#204) +* Allowed ``ModelLoader`` passively load partial model (#216) + +0.7.0 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Added Python 3.5 support (#187) +* Added support to use ``dict`` as ident for ``Model.get`` (#192) +* Added result loader (partial relationship support) (#13) +* Added documentation on relationship and transaction (#146) + + +GINO 0.6 +-------- + +This is also version 1.0 beta 2. + +Migrating to GINO 0.6 +^^^^^^^^^^^^^^^^^^^^^ + +1. Task Local +""""""""""""" + +We created a new Python package aiocontextvars_ from previous ``local.py``. If +you made use of the task local features, you should install this package. + +Previous ``gino.enable_task_local()`` and ``gino.disable_task_local()`` are +replaced by ``aiocontextvars.enable_inherit()`` and +``aiocontextvars.disable_inherit()``. However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars_ by default offers task +local even without ``enable_inherit()``, which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars_ is installed. + +There is no ``gino.get_local()`` and ``gino.reset_local()`` relevant in +aiocontextvars_. The similar thing is ``aiocontextvars.ContextVar`` instance +through its ``get()``, ``set()`` and ``delete()`` methods. + +Previous ``gino.is_local_root()`` is now +``not aiocontextvars.Context.current().inherited``. + +2. Engine +""""""""" + +GINO 0.6 hides ``asyncpg.Pool`` behind the new SQLAlchemy-alike +``gino.GinoEngine``. Instead of doing this in 0.5:: + + async with db.create_pool('postgresql://...') as pool: + # your code here + +You should change it to this in 0.6:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +This equals to:: + + engine = await gino.create_engine('postgresql://...') + db.bind = engine + try: + # your code here + finally: + db.bind = None + await engine.close() + +Or:: + + engine = await db.set_bind('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Or even this:: + + db = await gino.Gino('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Choose whichever suits you the best. + +Obviously ``GinoEngine`` doesn't provide ``asyncpg.Pool`` methods directly any +longer, but you can get the underlying ``asyncpg.Pool`` object through +``engine.raw_pool`` property. + +``GinoPool.get_current_connection()`` is now changed to ``current_connection`` +property on ``GinoEngine`` instances to support multiple engines. + +``GinoPool.execution_option`` is gone, instead ``update_execution_options()`` +on ``GinoEngine`` instance is available. + +``GinoPool().metadata`` is gone, ``dialect`` is still available. + +``GinoPool.release()`` is removed in ``GinoEngine`` and ``Gino``, the +``release()`` method on ``GinoConnection`` object should be used instead. + +These methods exist both in 0.5 ``GinoPool`` and 0.6 ``GinoEngine``: +``close()``, ``acquire()``, ``all()``, ``first()``, ``scalar()``, ``status()``. + +3. GinoConnection +""""""""""""""""" + +Similarly, ``GinoConnection`` in 0.6 is no longer a subclass of +``asyncpg.Connection``, instead it has a ``asyncpg.Connection`` instance, +accessable through ``GinoConnection.raw_connection`` property. + +``GinoConnection.metadata`` is deleted in 0.6, while ``dialect`` remained. + +``GinoConnection.execution_options()`` is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior. + +``GinoConnection.release()`` is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +``permanent=False`` to remain its previous behavior. + +And ``all()``, ``first()``, ``scalar()``, ``status()``, ``iterate()``, +``transaction()`` remained in 0.6. + +4. Query API +"""""""""""" + +All five query APIs ``all()``, ``first()``, ``scalar()``, ``status()``, +``iterate()`` now accept the same parameters as SQLAlchemy ``execute()``, +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter ``bind`` if they did. Just use the API on the ``GinoEngine`` or +``GinoConnection`` object instead. + +5. Transaction +"""""""""""""" + +Transaction interface is rewritten. Now in 0.6, a ``GinoTransaction`` object is +provided consistently from all 3 methods:: + + async with db.transaction() as tx: + # within transaction + + async with engine.transaction() as tx: + # within transaction + + async with engine.acquire() as conn: + async with conn.transaction() as tx: + # within transaction + +And different usage with ``await``:: + + tx = await db.transaction() + try: + # within transaction + await tx.commit() + except: + await tx.rollback() + raise + +The ``GinoConnection`` object is available at ``tx.connection``, while +underlying transaction object from database driver is available at +``tx.transaction`` - for asyncpg it is an ``asyncpg.transaction.Transaction`` +object. + +0.6.6 (2018-05-18) +^^^^^^^^^^^^^^^^^^ + +* Backported a fix for failing binary type (#225) + +0.6.5 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Abandoned 0.6.4 and keep 0.6.x stable +* Backported doc for transaction + +0.6.4 (2018-04-16) +^^^^^^^^^^^^^^^^^^ + +Abandoned version, please use 0.7.0 instead. + +0.6.3 (2018-04-08) +^^^^^^^^^^^^^^^^^^ + +* Added aiohttp support +* Added support for calling ``create()`` on model instances (Contributed by Kinware in #178 #180) +* Fixed ``get()`` by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184) + +0.6.2 (2018-03-24) +^^^^^^^^^^^^^^^^^^ + +* Fixed SQLAlchemy prefetch issue (#141) +* Fixed issue that mixin class on Model not working (#174) +* Added more documentation (Thanks Olaf Conradi for reviewing) + +0.6.1 (2018-03-18) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``create`` and ``drop`` for ``Enum`` type (#160) +* A bit more documentation (#159) + +0.6.0 (2018-03-14) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] API Refactored, ``Pool`` replaced with ``Engine`` + + * New API ``Engine`` replaced asyncpg ``Pool`` (#59) + * Supported different dialects, theoretically + * Used aiocontextvars_ instead of builtin task local (#89) +* [Breaking] Fixed query API with ``multiparams`` (executemany) to return correctly (#20) +* [Breaking] The query methods no longer accept the parameter ``bind`` +* [Breaking] ``Gino`` no longer exposes ``postgresql`` types +* Added ``echo`` on engine (#142) +* Added tests to cover 80% of code +* Added ``gino`` extension on ``SchemaItem`` for ``create_all`` and so on (#76 #106) +* Added ``gino`` extension on model classes for ``create()`` or ``drop()`` +* Added ``_update_request_cls`` on ``CRUDModel`` (#147) +* Rewrote the documentation (#146) + +.. _aiocontextvars: https://github.com/fantix/aiocontextvars + + +GINO 0.5 +-------- + +This is also version 1.0 beta 1. + +0.5.8 (2018-02-14) +^^^^^^^^^^^^^^^^^^ + +* Preparing for 0.6.0 which will be a breaking release +* Fixed wrong value of ``Enum`` in creation (Contributed by Sergey Kovalev in #126) + +0.5.7 (2017-11-24) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.6. + +* Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114) +* Added ``Model.outerjoin`` + +0.5.6 (2017-11-23) +^^^^^^^^^^^^^^^^^^ + +* Changed to use unnamed statement when possible (#80 #90) +* Added more example (Contributed by Kentoseth in #109) +* Added ``Model.join`` and made ``Model`` selectable (Contributed by Ádám Barancsuk in #112 #113) + +0.5.5 (2017-10-18) +^^^^^^^^^^^^^^^^^^ + +* Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87) +* Added ability to reset local storage (#84) +* Fixed bug in JSON property update +* Added update chaining feature + +0.5.4 (2017-10-04) +^^^^^^^^^^^^^^^^^^ + +* Updated example (Contributed by Kinware in #75) +* Added ``Model.insert`` (Contributed by Neal Wang in #63) +* Fixed issue that non-lazy acquiring fails dirty (#79) + +0.5.3 (2017-09-23) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``no module named cutils`` error (Contributed by Vladimir Goncharov in #73) + +0.5.2 (2017-09-10) +^^^^^^^^^^^^^^^^^^ + +* Added missing driver name on dialect (#67) +* Fixed dialect to support native decimal type (#67) + +0.5.1 (2017-09-09) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.0. + +* Reverted the extension, back to pure Python (#60) +* Used SQLAlchemy ``RowProxy`` +* Added ``first_or_404`` +* Fixed bug that ``GinoPool`` cannot be inherited + +0.5.0 (2017-09-03) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten + + * Extracted CRUD operations + * Core operations are moved to ``dialect`` and execution context + * Removed ``guess_model``, switched to explicit execution options + * Turned ``timeout`` parameter to an execution option + * Extracted ``pool``, ``connection`` and ``api`` from ``asyncpg_delegate`` +* Added support for SQLAlchemy execution options, and a few custom options +* [Breaking] Made `Model.select` return rows by default (#39) +* Moved `get_or_404` to extensions (#38) +* Added iterator on model classes (#43) +* Added Tornado extension (Contributed by Vladimir Goncharov) +* Added `Model.to_dict` (#47) +* Added an extension module to update `asyncpg.Record` with processed results + + +Early Development Releases +-------------------------- + +Considered as alpha releases. + + +0.4.1 (2017-08-20) +^^^^^^^^^^^^^^^^^^ + +* Support ``select`` on model instance + +0.4.0 (2017-08-15) +^^^^^^^^^^^^^^^^^^ + +* Made ``get_or_404`` more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31) +* Delegated ``sqlalchemy.__all__`` (Contributed by Neal Wang in #10 #33) +* [Breaking] Rewrote JSON/JSONB support (#29) +* Added ``lazy`` parameter on ``db.acquire`` (Contributed by Binghan Li in #32) +* Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34) +* Fixed ``iterate`` API to be compatible with asyncpg (#32) +* Unified exceptions +* [Breaking] Changed ``update`` API (#29) +* Bug fixes + +0.3.0 (2017-08-07) +^^^^^^^^^^^^^^^^^^ + +* Supported ``__table_args__`` (#12) +* Introduced task local to manage connection in context (#19) +* Added ``query.gino`` extension for in-place execution +* Refreshed README (#3) +* Adopted PEP 487 (Contributed by Tony Wang in #17 #27) +* Used ``weakref`` on ``__model__`` of table and query (Contributed by Tony Wang) +* Delegated asyncpg ``timeout`` parameter (Contributed by Neal Wang in #16 #22) + +0.2.3 (2017-08-04) +^^^^^^^^^^^^^^^^^^ + +* Supported any primary key (Contributed by Tony Wang in #11) + +0.2.2 (2017-08-02) +^^^^^^^^^^^^^^^^^^ + +* Supported SQLAlchemy result processor +* Added rich support on JSON/JSONB +* Bug fixes + +0.2.1 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Added ``update`` and ``delete`` API + +0.2.0 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Changed API, no longer reuses asyncpg API + +0.1.1 (2017-07-25) +^^^^^^^^^^^^^^^^^^ + +* Added ``db.bind`` +* API changed: parameter ``conn`` renamed to optional ``bind`` +* Delegated asyncpg Pool with ``db.create_pool`` +* Internal enhancement and bug fixes + +0.1.0 (2017-07-21) +^^^^^^^^^^^^^^^^^^ + +* First release on PyPI. diff --git a/docs/zh/1.0/_sources/tutorials.rst.txt b/docs/zh/1.0/_sources/tutorials.rst.txt new file mode 100644 index 0000000..6b3bb98 --- /dev/null +++ b/docs/zh/1.0/_sources/tutorials.rst.txt @@ -0,0 +1,9 @@ +Tutorials +========= + +.. toctree:: + :glob: + + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* diff --git a/docs/zh/1.0/_sources/tutorials/announcement.rst.txt b/docs/zh/1.0/_sources/tutorials/announcement.rst.txt new file mode 100644 index 0000000..f159e02 --- /dev/null +++ b/docs/zh/1.0/_sources/tutorials/announcement.rst.txt @@ -0,0 +1,297 @@ +官宣:Python 异步编程再添一利器 +=============================== + + GINO 填补了国内外 asyncio ORM 领域的空白 + +随着 Tornado\ [1]_ 和 asyncio\ [2]_ 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。\ +在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +`GINO `__ 了解一下! + +.. image:: ../images/python-gino.webp + :align: center + + +GINO 是谁 +--------- + +GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义\ [3]_\ 手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,\ +你肯定要问一句“这是谁”。 + +ORM,即关系对象映射(Object-Relational Mapping\ [4]_\ ),是一类开发人员喜闻乐见的效率工具,\ +它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢? + +因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性\ +(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),\ +创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:\ +性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 ``current_user.name`` +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试? + +传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,\ +但应用到大型项目中却十分考验开发人员的平均水平。 + +所有这些问题如果再放进异步编程的环境里,那就是 O(n\ :sup:`2`) 的复杂度了——哦不,是 +O(2\ :sup:`n`)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。 + +所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,\ +我索性在 1.0 稳定版发布的前夕做个年终总结。 + + +先说“方便快捷” +-------------- + + “方便快捷”主要说的是开发效率。 + +重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,\ +开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。 + +:: + + from gino import Gino + db = Gino() + class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + +这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟? + +没有错,这就是 SQLAlchemy ORM\ [5]_ 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core\ [6]_\ (SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道\ +(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic\ [7]_\ 、各种 SQLAlchemy 的增强插件\ [8]_\ 、专业领域的 PostGIS/geoalchemy\ [9]_\ +等,GINO 全都兼容。 + +是不是十分方便、十分快捷?不止这样。 + +GINO 一站式地解决了常用 CRUD 快捷方式\ [10]_\ 、上下文管理(aiocontextvars\ [11]_\ [12]_\ )、 +数据库事务封装和嵌套\ [13]_\ 、连接池管理和懒加载\ [14]_\ 等多项便捷功能,无额外依赖关系,即装即用。 + +:: + + daisy = await User.create(name="daisy") + await daisy.update(name="Daisy").apply() + +GINO 还提供了\ [15]_\ 各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado\ [1]_\ 、\ +aiohttp\ [16]_\ 、Sanic\ [17]_\ 、FastAPI\ [18]_\ /Starlette\ [19]_\ 、Quart\ [20]_\ +什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。 + +为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa\ [21]_ 的降维打击: + +1. 最少侵入型\ [22]_\ :SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。 +2. 终身不婚型\ [23]_\ :天生厌恶“对象”,只愿定义“表”,空手接 SQL。 +3. 火力全开型\ [24]_\ :最大程度的便利,非典型异步 ORM。 + +最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、\ +一秒可读百万行的 asyncpg\ [25]_\ ,以及 uvloop\ [26]_\ (可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,\ +深受俄罗斯和乌克兰人民的爱戴。 + + +再说“简单明了” +-------------- + + Explicit is better than implicit. + + Simple is better than complex. + + –The Zen of Python, PEP 20\ [27]_ + +Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,\ +因此 GINO 的很多设计都受到了明确性的影响。 + +比如说,GINO 的 ``Model`` 是完全无状态的普通 Python 对象(POPO\ [28]_\ —— 例如前面的 ``User`` +类,它的实例 ``daisy`` 就是内存里面的常规对象,你可以用 ``daisy.name`` 访问属性,也可以用 +``daisy.name = "DAISY"`` 来修改属性,或者用 ``u = User()`` 来创建新的实例,\ +这些操作都不会访问数据库,绝对绿色环保无毒副作用。 + +等到需要操作数据库的时候,你一定会有感知的。比如执行 ``INSERT`` 要用 +``u = await User.create()``\ ,而 ``UPDATE`` 则是 +``await u.update(name="Daisy").apply()``\ 。 + +.. hint:: + + 其中,``u.update(name="Daisy")`` 与 ``u.name = "Daisy"`` 类似,\ + 都是只在内存里修改对象的属性,不同的是 ``u.update()`` 还会返回一个包含本次变更的中间结果,\ + 对其执行 ``await xxx.apply()`` 则会将这些变更应用到数据库里。 + +这里的 ``await`` 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 ``await`` +就没有数据库操作。 + +另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders\ [29]_\ 。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 ``u = await User.get(1)`` 可以直接获取到 ID 为 ``1`` 的用户对象。\ +但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。\ +加载器的用法也是很简单的,比如一个用户可能写了很多本书: + +:: + + class Book(db.Model): + __tablename__ = "books" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + author_id = db.ForeignKey("users.id") + +然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者: + +:: + + query = Book.outerjoin(User).select() + loader = Book.load(author=User) + async for book in query.gino.load(loader).iterate(): + print(book.title, "written by", book.author.name) + +很简单的一个外连接查询 ``Book.outerjoin(User)``\ ,配合一个直观的加载器 +``Book.load(author=User)``\ ,就实现了: + +1. 执行 ``SELECT * FROM books LEFT JOIN users ON ...``\ ; +2. 将数据库返回结果的每一行中,属于 ``books`` 的字段加载成一个 ``Book`` 实例; +3. 然后将该行中剩下的属于 ``users`` 的字段加载成一个 ``User`` 实例; +4. 最后将 ``User`` 实例设置到 ``Book`` 实例的 ``author`` 属性上。 + +既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,\ +精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。 + + +优势与不足 +---------- + +随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM\ [30]_\ 、ORM\ [31]_\ (是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。 + +GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候\ +不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、\ +快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,\ +可以谨慎地用于生产环境了。 + +以下是近来统计到的关于 GINO 的应用案例: + +- 笔者工作上开发的一个服务:\ https://github.com/uc-cdis/metadata-service +- 还是笔者自己写的一个工具:\ https://github.com/fantix/aintq +- 一个汇率 API 服务:\ https://exchangeratesapi.io/ + + .. image:: ../images/exchangeratesapi.webp + :align: center + +- 各种 Telegram、Discord 的 Bot。 +- ArchLinux 用户包:\ https://aur.archlinux.org/packages/python-gino/ + + .. image:: ../images/archlinux.webp + :align: center + +- https://github.com/bryanforbes/gino-stubs/ +- 俄语教程:\ https://www.youtube.com/watch?v=WW4rOnfhiQY +- 高性能模板项目:\ https://github.com/leosussan/fastapi-gino-arq-uvicorn +- 还有几个商用的,但没征得同意就不贴出来了。 + +另外,GINO 还贴心地提供了中文文档\ [32]_\ ,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!): + +.. image:: ../images/docs.webp + :align: center + +GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能\ +(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。 + + +建设社会主义 +------------ + +GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 **4888 元**\ 的 PyCharm +专业版全家桶 License 一枚\ [33]_\ (没有发票)。 + +目前急需帮助的有: + +1. 各个 Web 框架插件的维护工作需要多人认领; +2. 更多的例子和文档,以及中文、俄文的翻译; +3. MySQL 的支持。 + +以及下面这些一直需要的帮助: + +1. 用 GINO,找 bug,提建议; +2. 修 bug,做功能,提 PR; +3. 维护社区,回答问题,参与讨论; +4. 最后也是最重要的:\ `去 GitHub 上给 GINO 加一颗星星! + `__ + +.. image:: ../images/happy-hacking.png + :align: center + + +关于作者 +-------- + +87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。\ +小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,\ +期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、\ +新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。\ +业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P + +特别感谢 `Tony Wang `__ 对 GINO 项目的贡献,以及\ `所有人 +`__\ 的贡献。 + + +参考文献 +-------- + +.. [1] Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado. +.. [2] Python Software Foundation. asyncio — 异步 I/O. + https://docs.python.org/zh-cn/3/library/asyncio.html. +.. [3] 维基百科. GNU. https://zh.wikipedia.org/wiki/GNU. +.. [4] 维基百科. 对象关系映射. + `https://zh.wikipedia.org/wiki/对象关系映射 + `__. +.. [5] 维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy. +.. [6] Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. + https://docs.sqlalchemy.org/en/13/core/index.html. +.. [7] Michael Bayer. Welcome to Alembic’s documentation!. + https://alembic.sqlalchemy.org/en/latest/. +.. [8] Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. + https://github.com/dahlia/awesome-sqlalchemy. +.. [9] Ricardo GarciaSilva. Does it have postgis support?. + https://github.com/python-gino/gino/issues/627. +.. [10] Fantix King. GINO 基础教程 - 增删改查. + https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations. +.. [11] Python Software Foundation. contextvars —Context Variables. + https://docs.python.org/3/library/contextvars.html. +.. [12] Fantix King. gino/aiocontextvars.py. + https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py. +.. [13] Fantix King. 数据库事务. + https://python-gino.org/docs/zh/master/how-to/transaction.html. +.. [14] Fantix King. 引擎与连接. + https://python-gino.org/docs/zh/master/explanation/engine.html. +.. [15] GINO Community. GINO Community. https://github.com/python-gino/. +.. [16] aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/. +.. [17] Sanic Community. SanicFramework. https://sanicframework.org/. +.. [18] Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/. +.. [19] Encode OSS. Starlette. https://www.starlette.io/. +.. [20] Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/. +.. [21] Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. + https://github.com/CanopyTax/asyncpgsa. +.. [22] Fantix King. 表结构定义 -GINO 引擎. + https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine. +.. [23] Fantix King. 表结构定义 - GINO core.  + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core. +.. [24] Fantix King. 表结构定义 - GINO ORM. + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm. +.. [25] MagicStack Inc. + asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. + https://github.com/MagicStack/asyncpg. +.. [26] MagicStack Inc. uvloop -Ultra fast asyncio event loop. + https://github.com/MagicStack/uvloop. +.. [27] Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/. +.. [28] Wikipedia. Plain old Java object. + https://en.wikipedia.org/wiki/Plain_old_Java_object. +.. [29] Fantix King. 加载器与关系. + https://python-gino.org/docs/zh/master/how-to/loaders.html. +.. [30] Andrey Bondar. Tortoise ORM. https://tortoise.github.io/. +.. [31] Encode OSS. ORM - An async ORM. https://github.com/encode/orm. +.. [32] Fantix King. 欢迎来到 GINO 的文档!. + https://python-gino.org/docs/zh/master/index.html. +.. [33] JetBrains s.r.o. Open Source Licenses. + https://www.jetbrains.com/community/opensource/#support. diff --git a/docs/zh/1.0/_sources/tutorials/fastapi.rst.txt b/docs/zh/1.0/_sources/tutorials/fastapi.rst.txt new file mode 100644 index 0000000..f256254 --- /dev/null +++ b/docs/zh/1.0/_sources/tutorials/fastapi.rst.txt @@ -0,0 +1,669 @@ +Build a FastAPI Server +====================== + +In this tutorial, we'll build a production-ready FastAPI_ server together. +The full functional example is available `here +`__. + +Our application stack will look like this: + +.. image:: ../images/gino-fastapi.svg + :align: center + + +.. _FastAPI: https://fastapi.tiangolo.com/ + + +Start a New Project +------------------- + +Instead of pip, let's use the shiny Poetry_ to manage our project. Follow the link to +`install Poetry `_, and create our new +project in an empty directory: + + +.. code-block:: console + + $ mkdir gino-fastapi-demo + $ cd gino-fastapi-demo + $ git init + $ poetry init + +Then follow the Poetry_ guide to finish the initialization - you may say "no" to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains ``gino-fastapi-demo``. + +.. _Poetry: https://python-poetry.org/ + + +Add Dependencies +---------------- + +FastAPI_ is built on top of the Starlette_ framework, so we shall use the `GINO +extension for Starlette `_. Simply run: + +.. code-block:: console + + $ poetry add gino[starlette] + +Then let's add FastAPI_, together with the lightning-fast ASGI_ server Uvicorn_, and +Gunicorn_ as a production application server: + +.. code-block:: console + + $ poetry add fastapi uvicorn gunicorn + +For database migration, we'll use Alembic_. Because it uses normal DB-API_, we need +psycopg_ here too: + +.. code-block:: console + + $ poetry add alembic psycopg2 + +At last, let's add pytest_ in the development environment for testing. We also want to +add the requests_ library to use the Starlette_ |TestClient|_: + +.. code-block:: console + + $ poetry add -D pytest requests + +.. hint:: + + With the steps above, Poetry_ will automatically create a virtualenv_ for you + behind the scene, and all the dependencies are installed there. We will assume + using this for the rest of the tutorial. But you're free to create your own + virtualenv_, and Poetry_ will honor it when it's activated. + +That's all, this is my ``pyproject.toml`` created by Poetry_, yours should look similar: + +.. code-block:: toml + + [tool.poetry] + name = "gino-fastapi-demo" + version = "0.1.0" + description = "" + authors = ["Fantix King "] + + [tool.poetry.dependencies] + python = "^3.8" + gino = {version = "^1.0", extras = ["starlette"]} + fastapi = "^0.54.1" + uvicorn = "^0.11.3" + gunicorn = "^20.0.4" + alembic = "^1.4.2" + psycopg2 = "^2.8.5" + + [tool.poetry.dev-dependencies] + pytest = "^5.4.1" + requests = "^2.23.0" + + [build-system] + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" + +.. image:: ../images/gino-fastapi-poetry.svg + :align: right + +And there's also an auto-generated ``poetry.lock`` file with the frozen versions. The +directory layout should look like the diagram on the right. Now let's add the two files +to the Git repository (we will skip showing these git operations in future steps): + +.. code-block:: console + + $ git add pyproject.toml poetry.lock + $ git commit -m 'add project dependencies' + +.. _Starlette: https://www.starlette.io/ +.. _ASGI: https://asgi.readthedocs.io/ +.. _Uvicorn: https://www.uvicorn.org/ +.. _Gunicorn: https://gunicorn.org/ +.. _Alembic: https://alembic.sqlalchemy.org/ +.. _DB-API: https://www.python.org/dev/peps/pep-0249/ +.. _psycopg: https://www.psycopg.org/ +.. _pytest: https://docs.pytest.org/ +.. _virtualenv: https://virtualenv.pypa.io/ +.. _requests: https://requests.readthedocs.io/ + + +Write a Simple Server +--------------------- + +Now let's write some Python code. + +We'll create an extra ``src`` directory to include all the Python files, as demonstrated +in the diagram below. This is known as the "`src layout +`_" providing a cleaner hierarchy. + +.. image:: ../images/gino-fastapi-src.svg + :align: right + +The root Python package of our project is named as ``gino_fastapi_demo``, under which we +will create two Python modules: + +* ``asgi`` as the ASGI entry point - we'll feed it to the ASGI server +* ``main`` to initialize our server + +Here's ``main.py``:: + + from fastapi import FastAPI + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + return app + +And we'll simply instantiate our application in ``asgi.py``:: + + from .main import get_app + + app = get_app() + +Then run ``poetry install`` to link our Python package into the ``PYTHONPATH`` in +development mode. We'll be able to start a Uvicorn development server after that: + +.. code-block:: console + + $ poetry install + Installing dependencies from lock file + + No dependencies to install or update + + - Installing gino-fastapi-demo (0.1.0) + + $ poetry run uvicorn gino_fastapi_demo.asgi:app --reload + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + INFO: Started reloader process [53010] + INFO: Started server process [53015] + INFO: Waiting for application startup. + INFO: Application startup complete. + +The ``--reload`` option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server. + +.. hint:: + + As mentioned previously, if you're in your own virtualenv, the command ``poetry run + uvicorn`` can be simplified as just ``uvicorn``. + + ``poetry run`` is a convenient shortcut to run the following command in the + virtualenv managed by Poetry. + + +Add GINO Extension +------------------ + +.. image:: ../images/gino-fastapi-config.svg + :align: right + +Now let's add GINO to our server. + +First of all, we need a way to configure the database. In this tutorial, we'll use the +`configuration system `_ from Starlette_. +Add ``src/gino_fastapi_demo/config.py`` as follows:: + + from sqlalchemy.engine.url import URL, make_url + from starlette.config import Config + from starlette.datastructures import Secret + + config = Config(".env") + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + DB_DSN = config( + "DB_DSN", + cast=make_url, + default=URL( + drivername=DB_DRIVER, + username=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + database=DB_DATABASE, + ), + ) + DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1) + DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16) + DB_ECHO = config("DB_ECHO", cast=bool, default=False) + DB_SSL = config("DB_SSL", default=None) + DB_USE_CONNECTION_FOR_REQUEST = config( + "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True + ) + DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1) + DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1) + +This config file will load from environment variable first, if not found then from a +file named ``.env`` from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this: + +.. code-block:: console + + $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload + +.. image:: ../images/gino-fastapi-env.svg + :align: right + +Or set them in the file ``.env`` (this file must not be committed into Git, remember to +add it to ``.gitignore``): + +.. code-block:: bash + + DB_HOST=localhost + DB_USER=postgres + +Now it's time to create a PostgreSQL_ database and set the connection variables +correctly here. This is usually something like ``createdb yourdbname``, but it may vary +across different platforms, so we won't cover this part in this tutorial. + +.. tip:: + + Alternatively, you could also set ``DB_DSN`` to for example + ``postgresql://user:password@localhost:5432/dbname`` to override the other individual + config values like ``DB_HOST`` defined before ``DB_DSN``. + + If defined, ``DB_DSN`` always have the higher priority over the individual ones, + regardless of where they are defined - even if ``DB_HOST`` is defined in environment + variable and ``DB_DSN`` is defined in ``.env`` file, ``DB_HOST`` is still ignored. + Default value doesn't count. + +.. image:: ../images/gino-fastapi-models.svg + :align: right + +Then, create a new Python sub-package ``gino_fastapi_demo.models`` to encapsulate +database-related code, and add the code below to +``src/gino_fastapi_demo/models/__init__.py``:: + + from gino.ext.starlette import Gino + + from .. import config + + db = Gino( + dsn=config.DB_DSN, + pool_min_size=config.DB_POOL_MIN_SIZE, + pool_max_size=config.DB_POOL_MAX_SIZE, + echo=config.DB_ECHO, + ssl=config.DB_SSL, + use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST, + retry_limit=config.DB_RETRY_LIMIT, + retry_interval=config.DB_RETRY_INTERVAL, + ) + +At last, modify ``src/gino_fastapi_demo/main.py`` to install the GINO extension: + +.. code-block:: diff + + from fastapi import FastAPI + + + +from .models import db + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + + db.init_app(app) + return app + +Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database: + +.. code-block:: console + + WARNING: Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading... + INFO: Shutting down + INFO: Waiting for application shutdown. + INFO: Application shutdown complete. + INFO: Finished server process [63562] + INFO: Started server process [63563] + INFO: Waiting for application startup. + INFO: Connecting to the database: postgresql://fantix:***@localhost + INFO: Database connection pool created: + INFO: Application startup complete. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Create Models and API +--------------------- + +.. image:: ../images/gino-fastapi-models-users.svg + :align: right + +It's time to implement the API now. Let's say we are building a user management service, +through which we could add users, list users and delete users. + +First of all, we need a database table ``users`` to store the data, mapped to a GINO +model named ``User``. We shall add the model in ``gino_fastapi_demo.models.users``:: + + from . import db + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode(), default="unnamed") + +.. image:: ../images/gino-fastapi-views.svg + :align: right + +The model definition is simple enough to explain itself. + +Then we only have to use it properly in the API implementation, for which we'll create a +new Python sub-package ``gino_fastapi_demo.views``, and a new module +``gino_fastapi_demo.views.users`` as follows:: + + from fastapi import APIRouter + from pydantic import BaseModel + + from ..models.users import User + + router = APIRouter() + + @router.get("/users/{uid}") + async def get_user(uid: int): + user = await User.get_or_404(uid) + return user.to_dict() + + class UserModel(BaseModel): + name: str + + @router.post("/users") + async def add_user(user: UserModel): + rv = await User.create(nickname=user.name) + return rv.to_dict() + + @router.delete("/users/{uid}") + async def delete_user(uid: int): + user = await User.get_or_404(uid) + await user.delete() + return dict(id=uid) + + def init_app(app): + app.include_router(router) + +The |APIRouter|_ holds our new APIs locally, and ``init_app`` is used to integrate it +into our FastAPI application. Here we want some `inversion of control`_: let's make the +APIs plugable, so that we don't have to import all possible future views manually. We +shall use the `Entry Points`_ feature to load the dependencies. Add this code below to +``gino_fastapi_demo.main``:: + + import logging + from importlib.metadata import entry_points + + logger = logging.getLogger(__name__) + + def load_modules(app=None): + for ep in entry_points()["gino_fastapi_demo.modules"]: + logger.info("Loading module: %s", ep.name) + mod = ep.load() + if app: + init_app = getattr(mod, "init_app", None) + if init_app: + init_app(app) + +.. hint:: + + If you're running Python < 3.8, you'll need this `importlib-metadata backport + `_. + +And call it in our application factory: + +.. code-block:: diff + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + db.init_app(app) + + load_modules(app) + return app + +Finally, define the entry points in ``pyproject.toml`` following the `Poetry document +for plugins `_: + +.. code-block:: toml + + [tool.poetry.plugins."gino_fastapi_demo.modules"] + "users" = "gino_fastapi_demo.views.users" + +Run ``poetry install`` again to activate the entry points - you may need to restart the +Uvicorn_ development server manually, as the reloader cannot capture the changes we made +to ``pyproject.toml``. + +Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven't created the database tables. + +.. |APIRouter| replace:: ``APIRouter`` +.. _APIRouter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control +.. _Entry Points: https://docs.python.org/3/library/importlib.metadata.html#entry-points + + +Integrate with Alembic +---------------------- + +To get started with Alembic_, run this command in the project root directory: + +.. code-block:: console + + $ poetry run alembic init migrations + +.. image:: ../images/gino-fastapi-alembic.svg + :align: right + +This will generate a new directory ``migrations`` where Alembic_ will store database +migration revisions. At the same time, an ``alembic.ini`` file is created in the project +root directory. Let's simply add all of them to Git control. + +For Alembic_ to use our data models defined with GINO (and of course the database +config), we need to modify ``migrations/env.py`` to connect with the GINO instance: + +.. code-block:: diff + + # add your model's MetaData object here + # for 'autogenerate' support + # from myapp import mymodel + # target_metadata = mymodel.Base.metadata + -target_metadata = None + +from gino_fastapi_demo.config import DB_DSN + +from gino_fastapi_demo.main import db, load_modules + + + +load_modules() + +config.set_main_option("sqlalchemy.url", str(DB_DSN)) + +target_metadata = db + +Then create our first migration revision with: + +.. code-block:: console + + $ poetry run alembic revision --autogenerate -m 'add users table' + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.autogenerate.compare] Detected added table 'users' + Generating migrations/versions/32c0feba61ea_add_users_table.py ... done + +The generated revision file should roughly look like this:: + + def upgrade(): + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("nickname", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + def downgrade(): + op.drop_table("users") + +.. hint:: + + Whenever there is a change to the database schema in the future, just modify the + GINO models and run ``alembic revision --autogenerate`` again to generate new + revisions to track the change. Remember to review the revision file - you may want + to adjust it. + +Eventually, let's apply this migration, by upgrading to the latest revision: + +.. code-block:: console + + $ poetry run alembic upgrade head + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.runtime.migration] Running upgrade -> 32c0feba61ea, add users table + +Now all the APIs should be fully operational, try with the Swagger UI. + + +Write the Tests +--------------- + +In order not to break our development database with running tests, let's create a +separate database to run tests. Apply this change to ``gino_fastapi_demo.config``: + +.. code-block:: diff + + config = Config(".env") + + +TESTING = config("TESTING", cast=bool, default=False) + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + +if TESTING: + + if DB_DATABASE: + + DB_DATABASE += "_test" + + else: + + DB_DATABASE = "gino_fastapi_demo_test" + DB_DSN = config( + +.. hint:: + + You need to run ``createdb`` to actually create the database. If you have set + ``DB_DATABASE`` in ``.env`` - e.g. ``DB_DATABASE=mydb``, the name of the testing + database should be ``mydb_test``. Or else, ``gino_fastapi_demo_test``. + +Then, let's create our pytest_ fixture in ``tests/conftest.py``:: + + import pytest + from alembic.config import main + from starlette.config import environ + from starlette.testclient import TestClient + + environ["TESTING"] = "TRUE" + + @pytest.fixture + def client(): + from gino_fastapi_demo.main import db, get_app + + main(["--raiseerr", "upgrade", "head"]) + + with TestClient(get_app()) as client: + yield client + + main(["--raiseerr", "downgrade", "base"]) + +.. image:: ../images/gino-fastapi-tests.svg + :align: right + +This fixture creates all the database tables before running the test, yield a Starlette_ +|TestClient|_, and drop all the tables with all the data after the test to maintain a +clean environment for the next test. + +Here's a sample test in ``tests/test_users.py``:: + + import uuid + + def test_crud(client): + # create + nickname = str(uuid.uuid4()) + r = client.post("/users", json=dict(name=nickname)) + r.raise_for_status() + + # retrieve + url = f"/users/{r.json()['id']}" + assert client.get(url).json()["nickname"] == nickname + + # delete + client.delete(url).raise_for_status() + assert client.get(url).status_code == 404 + +Then run the test: + +.. code-block:: console + + $ poetry run pytest + =========================== test session starts =========================== + platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 + rootdir: gino-fastapi-demo + collected 1 item + + tests/test_users.py . [100%] + + ============================ 1 passed in 1.21s ============================ + +.. |TestClient| replace:: ``TestClient`` +.. _TestClient: https://www.starlette.io/testclient/ + + +Notes for Production +-------------------- + +Given the popularity of Docker/Kubernetes, we'll build a ``Dockerfile`` for our demo: + +.. code-block:: dockerfile + + FROM python:3.8-alpine as base + + FROM base as builder + RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev + RUN pip install poetry + COPY . /src/ + WORKDIR /src + RUN python -m venv /env && . /env/bin/activate && poetry install + + FROM base + RUN apk add --no-cache postgresql-libs + COPY --from=builder /env /env + COPY --from=builder /src /src + WORKDIR /src + CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"] + +In this ``Dockerfile``, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn_ with +|UvicornWorker|_ from Uvicorn_ as the worker class for best production reliability. + +Let's review what we have in the project. + +.. image:: ../images/gino-fastapi-layout.svg + :align: center + +This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live: + +* Set ``DB_RETRY_LIMIT`` to a larger number to allow staring the application server + before the database is fully ready. +* Implement the same retry logic in ``migrations/env.py`` so that Alembic_ gets the same + functionality. +* Enable ``DB_SSL`` if needed. +* Write a ``docker-compose.yml`` for other developers to get a quick taste or even use + it for development. +* Enable CI_, install ``pytest-cov`` and use ``--cov-fail-under`` to guarantee coverage. +* Integrate static code analysis tools and security/CVE checking tools. +* Automate Alembic_ upgrade properly - e.g. after new version is deployed. +* Be aware of the common security attacks like CSRF_, XSS_, etc. +* Write load tests. + +Again, the source code of the demo is available `here +`__, +and the source of this tutorial is `here +`__. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking! + +.. |UvicornWorker| replace:: ``UvicornWorker`` +.. _UvicornWorker: https://www.uvicorn.org/deployment/#gunicorn +.. _CI: https://en.wikipedia.org/wiki/Continuous_integration +.. _CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/docs/zh/1.0/_sources/tutorials/tutorial.rst.txt b/docs/zh/1.0/_sources/tutorials/tutorial.rst.txt new file mode 100644 index 0000000..d9527f3 --- /dev/null +++ b/docs/zh/1.0/_sources/tutorials/tutorial.rst.txt @@ -0,0 +1,436 @@ +GINO Basics +=========== + +This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of: + +* RDBMS, especially PostgreSQL_ +* `Asynchronous programming in Python `_ + +Knowledge of SQLAlchemy_ is not required. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Introduction +------------ + +Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API. + +You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won't make your +code run faster, if not slower. Please read :doc:`/explanation/why` for more +information. + + +Installation +------------ + +To install GINO, run this command in your terminal: + +.. code-block:: console + + $ pip install gino + +This is the preferred method to install GINO, as it will always install the +most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +Alternatively, if you are using Poetry_ to manage your project dependencies, +you may want to run: + +.. code-block:: console + + $ poetry add gino + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ +.. _Poetry: https://python-poetry.org + + +Declare Models +-------------- + +First of all, we'll need a :class:`~gino.api.Gino` object, usually under the +name of ``db`` as a global variable:: + + from gino import Gino + + db = Gino() + +``db`` acts like a reference to the database, most database interactions will +go through it. + +"Model" is a basic concept in GINO, it is a Python class inherited from +:attr:`db.Model `. Each :class:`~gino.declarative.Model` +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let's declare a model:: + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + +By declaring this ``User`` class, we are actually defining a database table +named ``users``, with two columns ``id`` and ``nickname``. Note that the fixed +:attr:`~gino.declarative.Model.__tablename__` property is required. GINO +suggests singular for model names, and plural for table names. Each +:class:`db.Column ` property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to ``db`` types `here +`__ in the SQLAlchemy +documentation. + +.. note:: + + SQLAlchemy_ is a powerful ORM library for non-asynchronous programming in + Python, on top of which GINO is built. SQLAlchemy supports many popular + RDBMS including PostgreSQL and MySQL through different dialect + implementation, so that the same Python code can be compiled into different + SQL depending on the dialect you choose. GINO inherited this support too, + but for now there is only one dialect for PostgreSQL through asyncpg_. + +.. _asyncpg: https://github.com/MagicStack/asyncpg +.. _SQLAlchemy: https://www.sqlalchemy.org/ + +If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:: + + class Booking(db.Model): + __tablename__ = 'bookings' + + day = db.Column(db.Date) + booker = db.Column(db.String) + room = db.Column(db.String) + + _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey') + _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True) + _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room') + +It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +`here `__ in the +SQLAlchemy documentation. + +Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the :attr:`~gino.declarative.Model.__table_args__` attribute. In order +to e.g. define constraints in mixin classes, +:func:`~gino.declarative.declared_attr` is required. Please feel free to read +more about it in its API documentation. + + +Get Connected +------------- + +The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let's create a +PostgreSQL database for this tutorial: + +.. code-block:: console + + $ createdb gino + +Then we tell our ``db`` object to connect to this database:: + + import asyncio + + async def main(): + await db.set_bind('postgresql://localhost/gino') + + asyncio.get_event_loop().run_until_complete(main()) + +If this runs successfully, then you are connected to the newly created database. +Here ``postgresql`` indicates the database dialect to use (the default driver +is ``asyncpg``, you can explicitly specify that with ``postgresql+asyncpg://``, +or simply ``asyncpg://``), ``localhost`` is where the server is, and ``gino`` +is the name of the database. Check `here +`__ for more +information about how to compose this database URL. + +.. note:: + + Under the hood :meth:`~gino.api.Gino.set_bind` calls + :func:`~gino.create_engine` and bind the engine to this ``db`` object. GINO + engine is similar to SQLAlchemy engine, but not identical. Because GINO + engine is asynchronous, while the other is not. Please refer to the API + reference of GINO for more information. + +Now that we are connected, let's create the table in database (in the same +``main()`` method):: + + await db.gino.create_all() + +.. warning:: + + It is :meth:`db.gino.create_all `, + not :meth:`db.create_all `, because + ``db`` is inherited from SQLAlchemy :class:`~sqlalchemy.schema.MetaData`, + and :meth:`db.create_all ` is from + SQLAlchemy using non-asynchronous methods, which doesn't work with the + bound GINO engine. + + In practice :meth:`~gino.schema.GinoSchemaVisitor.create_all` is usually + not an ideal solution. To manage database schema, tool like Alembic_ is + recommended, please see how to :doc:`/how-to/alembic`. + +If you want to explicitly disconnect from the database, you can do this:: + + await db.pop_bind().close() + +Let's review the code we have so far together in one piece before moving on:: + + import asyncio + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + + async def main(): + await db.set_bind('postgresql://localhost/gino') + await db.gino.create_all() + + # further code goes here + + await db.pop_bind().close() + + + asyncio.get_event_loop().run_until_complete(main()) + +.. _Alembic: https://bitbucket.org/zzzeek/alembic + + +CRUD Operations +--------------- + +In order to operate on the database, one of GINO's core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations. + + +Create +^^^^^^ + +Let's start by creating a ``User``:: + + user = await User.create(nickname='fantix') + # This will cause GINO to execute this SQL with parameter 'fantix': + # INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname + +As mentioned previously, ``user`` object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:: + + print(f'ID: {user.id}') # 1 + print(f'Nickname: {user.nickname}') # fantix + +It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:: + + user = User(nickname='fantix') + user.nickname += ' (founder)' + await user.create() + +Retrieve +^^^^^^^^ + +To retrieve a model object from database by primary key, you can use the class +method :meth:`~gino.crud.CRUDModel.get` on the model class. Now let's retrieve +the same row:: + + user = await User.get(1) + # SQL (parameter: 1): + # SELECT users.id, users.nickname FROM users WHERE users.id = $1 + +Normal SQL queries are done through a class property +:attr:`~gino.crud.CRUDModel.query`. For example, let's retrieve all ``User`` +objects from database as a list:: + + all_users = await db.all(User.query) + # SQL: + # SELECT users.id, users.nickname FROM users + +Alternatively, you can use the ``gino`` extension on +:attr:`~gino.crud.CRUDModel.query`. This has exactly the same effect as above:: + + all_users = await User.query.gino.all() + # SQL: + # SELECT users.id, users.nickname FROM users + +.. note:: + + ``User.query`` is actually a SQLAlchemy query, with its own + non-asynchronous execution methods. GINO added this ``gino`` extension on + all executable SQLAlchemy clause objects to conveniently execute them in + the asynchronous way, so that it is even not needed to import the ``db`` + reference for execution. + +Now let's add some filters. For example, find all users with ID lower than 10:: + + founding_users = await User.query.where(User.id < 10).gino.all() + # SQL (parameter: 10): + # SELECT users.id, users.nickname FROM users WHERE users.id < $1 + +Read more `here `__ +about writing queries, because the query object is exactly from SQLAlchemy core. + +.. warning:: + + Once you get a model object, it is purely in memory and fully detached from + the database. That means, if the row is externally updated, the object + values remain unchanged. Likewise, changes made to the object won't affect + the database values. + + Also, GINO keeps no track of model objects, therefore getting the same row + twice returns two different object with identical values. Modifying one + does not magically affect the other one. + + Different than traditional ORMs, the GINO model objects are more like + objective SQL results, rather than stateful ORM objects. In order to adapt + for asynchronous programming, GINO is designed to be that simple. That's + also why GINO Is Not ORM. + +Sometimes we want to get only one object, for example getting the user by name +when logging in. There's a shortcut for this scenario:: + + user = await User.query.where(User.nickname == 'fantix').gino.first() + # SQL (parameter: 'fantix'): + # SELECT users.id, users.nickname FROM users WHERE users.nickname = $1 + +If there is no user named "fantix" in database, ``user`` will be ``None``. + +And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +:meth:`~gino.crud.CRUDModel.select` class method:: + + name = await User.select('nickname').where(User.id == 1).gino.scalar() + # SQL (parameter: 1): + # SELECT users.nickname FROM users WHERE users.id = $1 + print(name) # fantix + +Or get the count of all users:: + + population = await db.func.count(User.id).gino.scalar() + # SQL: + # SELECT count(users.id) AS count_1 FROM users + print(population) # 17 for example + + +Update +^^^^^^ + +Then let's try to make some modifications. In this example we'll mixin some +retrieve operations we just tried. :: + + # create a new user + user = await User.create(nickname='fantix') + + # get its name + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + assert name == user.nickname # they are both 'fantix' before the update + + # modification here + await user.update(nickname='daisy').apply() + # SQL (parameters: 'daisy', 1): + # UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname + print(user.nickname) # daisy + + # get its name again + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + print(name) # daisy + assert name == user.nickname # they are both 'daisy' after the update + +So :meth:`~gino.crud.CRUDModel.update` is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +:meth:`~gino.crud.UpdateRequest.apply` call makes the update happen in database. + +.. note:: + + GINO explicitly split the in-memory update and SQL update into two methods: + :meth:`~gino.crud.CRUDModel.update` and + :meth:`~gino.crud.UpdateRequest.apply`. :meth:`~gino.crud.CRUDModel.update` + will update the in-memory model object and return an + :class:`~gino.crud.UpdateRequest` object which contains all the + modifications. A following :meth:`~gino.crud.UpdateRequest.apply` on + :class:`~gino.crud.UpdateRequest` object will apply these recorded + modifications to database by executing a compiled SQL. + +.. tip:: + + :class:`~gino.crud.UpdateRequest` object has another method named + :meth:`~gino.crud.UpdateRequest.update` which works the same as the one + on model object, just that it combines the new modifications together with + the ones already recorded in current :class:`~gino.crud.UpdateRequest` + object, and it returns the same :class:`~gino.crud.UpdateRequest` object. + That means, you can chain the updates and end up with one + :meth:`~gino.crud.UpdateRequest.apply`, or make use of the + :class:`~gino.crud.UpdateRequest` object to combine several updates in a + batch. + +:meth:`~gino.crud.CRUDModel.update` on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the :meth:`~gino.crud.CRUDModel.update` on model class level, with a +bit difference:: + + await User.update.values(nickname='Founding Member ' + User.nickname).where( + User.id < 10).gino.status() + # SQL (parameter: 'Founding Member ', 10): + # UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2 + + name = await User.select('nickname').where( + User.id == 1).gino.scalar() + print(name) # Founding Member fantix + +There is no :class:`~gino.crud.UpdateRequest` here, everything is again +SQLAlchemy clause, its +`documentation `_ here for +your reference. + + +Delete +^^^^^^ + +At last. Deleting is similar to updating, but way simpler. :: + + + user = await User.create(nickname='fantix') + await user.delete() + # SQL (parameter: 1): + # DELETE FROM users WHERE users.id = $1 + print(await User.get(user.id)) # None + +.. hint:: + + Remember the model object is in memory? In the last :func:`print` + statement, even though the row is already deleted in database, the object + ``user`` still exists with its values untouched. + +Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):: + + await User.delete.where(User.id > 10).gino.status() + # SQL (parameter: 10): + # DELETE FROM users WHERE users.id > $1 + + +With basic :doc:`/how-to/crud`, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking! diff --git a/docs/zh/1.0/_static/css/gino.css b/docs/zh/1.0/_static/css/gino.css new file mode 100644 index 0000000..c26ddbc --- /dev/null +++ b/docs/zh/1.0/_static/css/gino.css @@ -0,0 +1,806 @@ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: 100%; + box-sizing: border-box; + font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; +} + +body { + background-color: #F0F0F0; + font-weight: 500; +} + +a { + color: #3E6CDE; +} + +code, kbd, samp { + font-family: monospace; + background-color: #f8f8f8; + border: 1px solid #f0f0f0; + padding: 0 4px; + border-radius: 2px; +} + +strong { + font-weight: 700; +} + +:root { + --v-primary-base: #212B73; + --v-secondary-base: #AD755C; + --v-accent-base: #EEF5FF; + --v-error: #FF5252; + --v-info: #BDC8F1; + --v-info-light: #3E6CDE; + --v-success: #4CAF50; + --v-success-light: rgba(76, 175, 80, 0.2); + --v-warning: #FFC107; + --v-warning-light: rgba(255, 193, 7, 0.2); + --v-border: #444b54; + --v-border-light: #c0cee1; +} + +nav { + background-color: rgba(33, 43, 115, 0.95); + -webkit-box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); +} + +.nav-wrapper { + padding: 4px 16px; + display: flex; + align-items: center; +} + +nav .brand-logo { + position: relative; + display: flex; + align-items: center; + left: 0; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +.breadcrumbs { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + -ms-overflow-style: none; +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs .breadcrumb, +.breadcrumbs .breadcrumb::before { + content: '>'; + font-size: 16px; + white-space: nowrap; +} + + +.btn-flat { + display: flex; + align-items: center; + font-weight: 500; + margin-left: 22px; + text-decoration: none; + color: #fff; + padding: 14px; + font-size: 14px; + -webkit-transition: .2s; + transition: .2s; +} + +.btn-flat:hover { + color: #f8d230; + text-shadow: #f8d230 0 0 5px; +} + +a.brackets:before, +span.brackets:empty, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +a.footnote-reference { + vertical-align: super; + font-size: 0.8em; +} + +.footnote dt { + float: left; +} + +div.search { + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +} + +div.search i { + position: absolute; + left: 10px; + transition: 0.3s; +} + +#search:focus + i { + color: var(--v-primary-base); +} + +#search { + flex: 0.1 1 192px; + background-color: rgba(255, 255, 255, 0.16); + border-radius: 28px; + height: 38px; + padding: 0 32px 0 42px; + margin: 0; + border: none; + transition: 0.3s; + color: rgba(255, 255, 255, 0.32); + font-size: 14px; +} + +#search:focus { + border: none; + background-color: #FFFFFF; + color: #000000; +} + +#search-results { + position: absolute; + left: 0; + right: -250px; + max-height: 61.8vh; + background-color: rgba(0, 0, 0, 0.8); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + overflow: scroll; + top: 51px; + padding: 16px; +} + +#search-results li { + float: none; + line-height: 1.5; + font-size: 14px; +} + +#search-results li a { + color: #4E7CEE; + padding: 0; +} + +#search-results li .context { + padding: 0 0 24px 24px; +} +#search-results .highlighted { + color: #f8d230; +} + +.theme-dark { + color: #FFFFFF; +} + +.spacer { + flex-grow: 1; +} + +.img { + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.sidenav { + top: 64px; + z-index: 900; + box-shadow: none; + bottom: 0; + height: auto; +} + +.col.body { + padding: 0; +} + +#main-content { + padding: 0 24px; + background-color: white; +} + +.sidenav ul li:first-of-type { + display: none; +} + +.sidenav ul ul li:first-of-type { + display: block; +} + +.sidenav .caption { + font-size: 1.2em; + color: var(--v-primary-base); + padding: 16px 32px 0 48px; + position: relative; + font-weight: 600; +} + +.sidenav .caption::before { + position: absolute; + display: block; + content: " "; + width: 20px; + height: 20px; + left: 16px; + top: 20px; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.sidenav li > a { + font-size: 14px; + line-height: 14px; + padding: 12px 24px 12px 48px; + height: auto; +} + +.sidenav li li > a { + padding-left: 70px; +} + +.sidenav .caption:nth-of-type(1)::before { + background-image: url(../images/tutorials-icon.svg); +} +.sidenav .caption:nth-of-type(2)::before { + background-image: url(../images/how-to-icon.svg); +} +.sidenav .caption:nth-of-type(3)::before { + background-image: url(../images/explanation-logo.svg); +} +.sidenav .caption:nth-of-type(4)::before { + background-image: url(../images/reference-logo.svg); +} + +.sidenav a.current { + background-color: var(--v-accent-base); +} + +.github { + margin-left: 16px; + transition: 0.3s; +} + +.github:hover { + text-shadow: #FFFFFF 0 0 5px; +} + +.github i { + font-size: 40px; +} + +.right-nav.col { + padding: 0; +} + +.table-of-contents { + padding: 16px; + top: 64px; + bottom: 0; + overflow-y: auto; +} + +.table-of-contents li { + padding: 0; +} + +.table-of-contents > ul > li > a { + display: none; +} + +.table-of-contents > ul ul > li > a.active, +.table-of-contents > ul ul > li > a:hover { + padding-left: 14px; +} + +.table-of-contents > ul ul ul > li > a.active, +.table-of-contents > ul ul ul > li > a:hover { + padding-left: 30px; +} + +.table-of-contents > ul ul ul > li > a { + padding-left: 32px; +} + +.table-of-contents > ul ul ul ul > li > a.active, +.table-of-contents > ul ul ul ul > li > a:hover { + padding-left: 46px; +} + +.table-of-contents > ul ul ul ul > li > a { + padding-left: 48px; +} + +.table-of-contents a { + height: auto; + padding-top: 2px; + padding-bottom: 2px; +} + +.table-of-contents a.active, +.table-of-contents a:hover { + color: var(--v-primary-base); + border-left: 2px solid var(--v-primary-base); + font-weight: 500; +} + +.table-of-contents #version-selector hr { + margin-top: 48px; +} + +.table-of-contents #version-selector div { + margin-top: 16px; + color: #757575; +} + +.table-of-contents #version-selector a { + color: #757575; +} + +.table-of-contents .add-your-lang, +.table-of-contents .add-your-lang a, +.table-of-contents .add-your-lang a:active, +.table-of-contents .add-your-lang a:hover { + border-left: none; + font-size: 14px; + text-decoration: underline; + text-align: right; + margin-top: 8px; + color: #aaa; + font-weight: normal; +} + +.table-of-contents #version-selector span, +.table-of-contents #version-selector a, +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + border-left: none; + padding: 8px; + margin-left: 16px; +} + +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + color: var(--v-info-light); +} + +.table-of-contents #version-selector span { + color: var(--v-info-light); +} + +h1 { + font-size: 32px; + margin: 32px 0 20px 0; + font-weight: 600; +} + +h2 { + font-size: 26px; + margin: 18px 0 12px 0; + font-weight: 600; +} + +h3 { + font-size: 22px; + margin: 17px 0 11px 0; + font-weight: 600; +} + +h4 { + font-size: 18px; + margin: 14px 0 9px 0; + font-weight: 600; +} + +h5 { + font-size: 16px; + margin: 13px 0 8px 0; + font-weight: 600; +} + +h6 { + font-size: 15px; + margin: 12px 0 7px 0; + font-weight: 600; +} + +a.headerlink { + padding-left: 8px; + visibility: hidden; + position: absolute; + font-family: sans-serif; +} + +:hover > a.headerlink { + visibility: visible; +} + +.section { + /*margin-top: -64px;*/ + /*padding-top: 64px;*/ +} + +.highlight { + background-color: rgba(0, 0, 0, 0.8); + clear: both; +} + +.highlight pre { + border: 1px solid var(--v-border-light); + padding: 1em; + font-size: 14px; + overflow-x: auto; +} + +.body li p { + margin: 0; +} + +.body ul li { + list-style-type: disc; + margin-left: 2rem; +} + +.body ul li li { + list-style-type: circle; +} + +.body ul li li li { + list-style-type: square; +} + +.admonition { + border-radius: 4px; + padding: 1px 1rem; + box-shadow: #D1CECE 0 0 4px; +} + +.admonition-title { + font-size: 18px; + font-weight: 600; + color: #3E6CDE; +} + +.admonition-title:before { + content: " "; + display: inline-block; + width: 20px; + height: 20px; + margin: 0 8px -4px 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.admonition.note, +.admonition.seealso { + background-color: #F5FAFD; +} + +.admonition.note .admonition-title:before, +.admonition.seealso .admonition-title:before { + background-image: url(../images/icon-note.svg); +} + +.admonition.tip, +.admonition.hint { + background-color: #F5FDF6; +} + +.admonition.hint .admonition-title:before { + background-image: url(../images/icon-hint.svg); +} + +.admonition.warning { + background-color: #FDF5F5; +} + +.admonition.warning .admonition-title:before { + background-image: url(../images/icon-warning.svg); +} + +.admonition.tip .admonition-title:before { + background-image: url(../images/icon-info.svg); +} + +.body .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 700px; +} + +.body .section ul.boxed-nav li { + border-radius: 4px; + box-shadow: #D1CECE 0 0 4px; + padding: 10px; + margin: 25px 0 0; + width: 340px; + list-style: none; + color: #202A73; + font-weight: 500; + overflow: hidden; +} + +.body .section ul.boxed-nav li div { + background-color: #F4F7FC; + border-radius: 4px; + height: 110px; + padding: 30px 30px 0; + background-image: url(../images/box-bg-dec.svg); + background-repeat: no-repeat; + background-position: -15px -10px; +} + +.body .section ul.boxed-nav li:nth-of-type(3n) div { + background-image: url(../images/box-bg-dec-2.svg); +} + +.body .section ul.boxed-nav li:nth-of-type(3n+1) div { + background-image: url(../images/box-bg-dec-3.svg); +} + +.body .section ul.boxed-nav li a img { + width: 42px; + margin: 0 30px 30px 0; + float: left; +} + +.body .section ul.boxed-nav li p { + font-size: 18px; + color: #212B73; + line-height: 18px; + margin-bottom: 8px; +} + +.body .section ul.boxed-nav li p:nth-of-type(2) { + font-size: 14px; + word-break: break-word; +} + +.body .section ul.boxed-nav p a, +.body .section ul.boxed-nav p a:visited { + color: #212B73; +} + +p.divio { + position: absolute; + font-size: 0.8em; + color: #aaa; + margin-top: 40px; +} + +p.divio a, p.divio a:visited { + color: #aaa; +} + +.github-stars { + display: flex; + align-items: center; + padding: 0; + margin-left: 32px; + border-radius: 27px; + font-size: 14px; + font-weight: 500; +} + +.github-stars div { + border: 1px solid #fff; + line-height: 27px; + background-color: #1E2B78; + transition: 0.1s; + height: 29px; +} + +.github-stars div.left { + border-bottom-left-radius: 27px; + border-top-left-radius: 27px; + border-right: none; + padding: 0 7px 0 34px; + position: relative; +} + +.github-stars div.left .github-logo { + position: absolute; + height: 29px; + top: -1px; + left: -1px; +} + +.github-stars div.right { + border-bottom-right-radius: 27px; + border-top-right-radius: 27px; + padding: 0 7px 0 7px; + display: flex; + align-items: center; +} + +.github-stars div.right img { + height: 14px; + margin-right: 7px; +} + +.github-stars:hover { + color: white; + text-shadow: none; + box-shadow: #ffffff 0 0 5px; +} + +.btn-large.white img { + width: 32px; + height: 32px; + margin-top: 6px; +} + +.btn-large.white div { + color: #3E6CDE; + position: absolute; + bottom: 0; + width: 56px; + line-height: 18px; + font-size: 14px; + font-weight: 600; +} + +.sidenav-overlay { + z-index: 897; +} + +nav a.sidenav-trigger { + height: 24px; + margin: 16px 16px 16px 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +nav a.sidenav-trigger hr { + width: 24px; + margin: 0; + border: 0; + background-color: #fff; + height: 2px; +} + +.fixed-action-btn { + display: none; +} + +@media (min-width: 993px) { + main { + padding-left: 300px; + } + + #main-content { + padding: 0 48px; + margin: 16px; + border-radius: 4px; + } + + .admonition { + margin: 1.5rem 2rem; + padding: 1px 3rem; + } + + img.align-center { + max-width: 100%; + display: block; + margin: 0 auto; + } + + img.align-left { + max-width: 100%; + float: left; + margin: 0 2rem 1.5rem 0; + } + + img.align-right { + max-width: 100%; + float: right; + margin: 0 0 1.5rem 2rem; + } + + .table-of-contents { + width: calc(25vw - 75px); + } + + p.divio { + margin-left: -47px; + } + + .btn-flat { + margin-left: 22px; + padding: 14px; + } + + .sidenav { + top: 64px; + } + + nav a.sidenav-trigger { + display: none; + } +} + +@media (max-width: 992px) { + img.align-left, + img.align-center, + img.align-right { + display: block; + margin: 1.5rem auto; + max-width: 100%; + } + + .admonition { + overflow: scroll; + } + + #main-content { + padding-bottom: 56px; + } + + #main-content p { + overflow-x: scroll; + } + + main > .row { + margin-bottom: 0; + } + + .breadcrumbs { + display: none; + } + + .btn-flat { + margin-left: 10px; + padding: 5px; + } +} + +@media (max-width: 740px) { + #search-container { + display: none; + } +} + +@media (max-width: 600px) { + .sidenav { + top: 56px; + } + + .fixed-action-btn { + display: block; + } +} + +@media (max-width: 510px) { + .github-stars { + display: none; + } +} diff --git a/docs/zh/1.0/_static/css/materialize.min.css b/docs/zh/1.0/_static/css/materialize.min.css new file mode 100644 index 0000000..74b1741 --- /dev/null +++ b/docs/zh/1.0/_static/css/materialize.min.css @@ -0,0 +1,13 @@ +/*! + * Materialize v1.0.0 (http://materializecss.com) + * Copyright 2014-2017 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/docs/zh/1.0/_static/favicon.ico b/docs/zh/1.0/_static/favicon.ico new file mode 100644 index 0000000..07b38bc Binary files /dev/null and b/docs/zh/1.0/_static/favicon.ico differ diff --git a/docs/zh/1.0/_static/images/box-bg-dec-2.svg b/docs/zh/1.0/_static/images/box-bg-dec-2.svg new file mode 100644 index 0000000..0c1e246 --- /dev/null +++ b/docs/zh/1.0/_static/images/box-bg-dec-2.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/zh/1.0/_static/images/box-bg-dec-3.svg b/docs/zh/1.0/_static/images/box-bg-dec-3.svg new file mode 100644 index 0000000..0d757ca --- /dev/null +++ b/docs/zh/1.0/_static/images/box-bg-dec-3.svg @@ -0,0 +1,25 @@ + + + + Path 10 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/docs/zh/1.0/_static/images/box-bg-dec.svg b/docs/zh/1.0/_static/images/box-bg-dec.svg new file mode 100644 index 0000000..a6e38d5 --- /dev/null +++ b/docs/zh/1.0/_static/images/box-bg-dec.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/zh/1.0/_static/images/explanation-logo.svg b/docs/zh/1.0/_static/images/explanation-logo.svg new file mode 100644 index 0000000..98df7c3 --- /dev/null +++ b/docs/zh/1.0/_static/images/explanation-logo.svg @@ -0,0 +1,20 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/how-to-icon.svg b/docs/zh/1.0/_static/images/how-to-icon.svg new file mode 100644 index 0000000..6506741 --- /dev/null +++ b/docs/zh/1.0/_static/images/how-to-icon.svg @@ -0,0 +1,24 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/icon-hint.svg b/docs/zh/1.0/_static/images/icon-hint.svg new file mode 100644 index 0000000..db86c44 --- /dev/null +++ b/docs/zh/1.0/_static/images/icon-hint.svg @@ -0,0 +1,27 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/icon-info.svg b/docs/zh/1.0/_static/images/icon-info.svg new file mode 100644 index 0000000..c4690a0 --- /dev/null +++ b/docs/zh/1.0/_static/images/icon-info.svg @@ -0,0 +1,27 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/icon-note.svg b/docs/zh/1.0/_static/images/icon-note.svg new file mode 100644 index 0000000..03308b0 --- /dev/null +++ b/docs/zh/1.0/_static/images/icon-note.svg @@ -0,0 +1,21 @@ + + + + 编组 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/icon-warning.svg b/docs/zh/1.0/_static/images/icon-warning.svg new file mode 100644 index 0000000..4e3ea02 --- /dev/null +++ b/docs/zh/1.0/_static/images/icon-warning.svg @@ -0,0 +1,29 @@ + + + + 编组 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/language.svg b/docs/zh/1.0/_static/images/language.svg new file mode 100644 index 0000000..b61c7d4 --- /dev/null +++ b/docs/zh/1.0/_static/images/language.svg @@ -0,0 +1,16 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/reference-logo.svg b/docs/zh/1.0/_static/images/reference-logo.svg new file mode 100644 index 0000000..b79d156 --- /dev/null +++ b/docs/zh/1.0/_static/images/reference-logo.svg @@ -0,0 +1,22 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/images/tutorials-icon.svg b/docs/zh/1.0/_static/images/tutorials-icon.svg new file mode 100644 index 0000000..ff76008 --- /dev/null +++ b/docs/zh/1.0/_static/images/tutorials-icon.svg @@ -0,0 +1,21 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/js/documentation_options.js b/docs/zh/1.0/_static/js/documentation_options.js new file mode 100644 index 0000000..53ef425 --- /dev/null +++ b/docs/zh/1.0/_static/js/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.0.2.dev0', + LANGUAGE: 'zh', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/zh/1.0/_static/js/gino.js b/docs/zh/1.0/_static/js/gino.js new file mode 100644 index 0000000..1690ada --- /dev/null +++ b/docs/zh/1.0/_static/js/gino.js @@ -0,0 +1,642 @@ +$u = _.noConflict(); + +function splitQuery(query) { + return query.split(/\s+/); +} + +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2 +}; + +var Search = { + _index : null, + + setIndex: function(index) { + this._index = index; + }, + + performObjectSearch : function(object, otherterms) { + var filenames = this._index.filenames; + var docnames = this._index.docnames; + var objects = this._index.objects; + var objnames = this._index.objnames; + var titles = this._index.titles; + + var i; + var results = []; + + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + var fullnameLower = fullname.toLowerCase() + if (fullnameLower.indexOf(object) > -1) { + var score = 0; + var parts = fullnameLower.split('.'); + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower == object || parts[parts.length - 1] == object) { + score += Scorer.objNameMatch; + // matches in last name + } else if (parts[parts.length - 1].indexOf(object) > -1) { + score += Scorer.objPartialMatch; + } + var match = objects[prefix][name]; + var objname = objnames[match[1]][2]; + var title = titles[match[0]]; + // If more than one term searched for, we require other words to be + // found in the name/title/description + if (otherterms.length > 0) { + var haystack = (prefix + ' ' + name + ' ' + + objname + ' ' + title).toLowerCase(); + var allfound = true; + for (i = 0; i < otherterms.length; i++) { + if (haystack.indexOf(otherterms[i]) == -1) { + allfound = false; + break; + } + } + if (!allfound) { + continue; + } + } + var descr = objname + (', in ') + title; + + var anchor = match[3]; + if (anchor === '') + anchor = fullname; + else if (anchor == '-') + anchor = objnames[match[1]][1] + '-' + fullname; + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) { + score += Scorer.objPrio[match[2]]; + } else { + score += Scorer.objPrioDefault; + } + results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]); + } + } + } + + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch : function(searchterms, excluded, terms, titleterms) { + var docnames = this._index.docnames; + var filenames = this._index.filenames; + var titles = this._index.titles; + + var i, j, file; + var fileMap = {}; + var scoreMap = {}; + var results = []; + + // perform the search on the required terms + for (i = 0; i < searchterms.length; i++) { + var word = searchterms[i]; + var files = []; + var _o = [ + {files: terms[word], score: Scorer.term}, + {files: titleterms[word], score: Scorer.title} + ]; + // add support for partial matches + if (word.length > 2) { + for (var w in terms) { + if (w.match(word) && !terms[word]) { + _o.push({files: terms[w], score: Scorer.partialTerm}) + } + } + for (var w in titleterms) { + if (w.match(word) && !titleterms[word]) { + _o.push({files: titleterms[w], score: Scorer.partialTitle}) + } + } + } + + // no match but word was a required one + if ($u.every(_o, function(o){return o.files === undefined;})) { + break; + } + // found search word in contents + $u.each(_o, function(o) { + var _files = o.files; + if (_files === undefined) + return + + if (_files.length === undefined) + _files = [_files]; + files = files.concat(_files); + + // set score for the word in each file to Scorer.term + for (j = 0; j < _files.length; j++) { + file = _files[j]; + if (!(file in scoreMap)) + scoreMap[file] = {}; + scoreMap[file][word] = o.score; + } + }); + + // create the mapping + for (j = 0; j < files.length; j++) { + file = files[j]; + if (file in fileMap && fileMap[file].indexOf(word) === -1) + fileMap[file].push(word); + else + fileMap[file] = [word]; + } + } + + // now check if the files don't contain excluded terms + for (file in fileMap) { + var valid = true; + + // check if all requirements are matched + var filteredTermCount = // as search terms with length < 3 are discarded: ignore + searchterms.filter(function(term){return term.length > 2}).length + if ( + fileMap[file].length != searchterms.length && + fileMap[file].length != filteredTermCount + ) continue; + + // ensure that none of the excluded terms is in the search result + for (i = 0; i < excluded.length; i++) { + if (terms[excluded[i]] == file || + titleterms[excluded[i]] == file || + $u.contains(terms[excluded[i]] || [], file) || + $u.contains(titleterms[excluded[i]] || [], file)) { + valid = false; + break; + } + } + + // if we have still a valid result we can add it to the result list + if (valid) { + // select one (max) score for the file. + // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... + var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); + results.push([docnames[file], titles[file], '', null, score, filenames[file]]); + } + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurrence, the + * latter for highlighting it. + */ + makeSearchSummary : function(htmlText, keywords, hlwords) { + var text = Search.htmlToText(htmlText); + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
    ').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; + }, + + htmlToText : function(htmlString) { + var htmlElement = document.createElement('span'); + htmlElement.innerHTML = htmlString; + $(htmlElement).find('.headerlink').remove(); + docContent = $(htmlElement).find('[role=main]')[0]; + if(docContent === undefined) { + console.warn("Content block not found. Sphinx search tries to obtain it " + + "via '[role=main]'. Could you check your theme or template."); + return ""; + } + return docContent.textContent || docContent.innerText; + }, + + query: function(query) { + this.out = $('#search-results'); + this.out.empty(); + this.output = $(''),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0'),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); \ No newline at end of file diff --git a/docs/zh/1.0/_static/js/underscore.js b/docs/zh/1.0/_static/js/underscore.js new file mode 100644 index 0000000..5b55f32 --- /dev/null +++ b/docs/zh/1.0/_static/js/underscore.js @@ -0,0 +1,31 @@ +// Underscore.js 1.3.1 +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, +h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= +b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== +null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= +function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= +e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= +function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, +c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; +b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, +1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; +b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; +b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), +function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ +u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= +function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= +true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/docs/zh/1.0/_static/logo.svg b/docs/zh/1.0/_static/logo.svg new file mode 100644 index 0000000..78bb2b4 --- /dev/null +++ b/docs/zh/1.0/_static/logo.svg @@ -0,0 +1,42 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/_static/pygments.css b/docs/zh/1.0/_static/pygments.css new file mode 100644 index 0000000..c3bec81 --- /dev/null +++ b/docs/zh/1.0/_static/pygments.css @@ -0,0 +1,72 @@ +.highlight .hll { background-color: #49483e } +.highlight { background: #272822; color: #f8f8f2 } +.highlight .c { color: #75715e } /* Comment */ +.highlight .err { color: #960050; background-color: #1e0010 } /* Error */ +.highlight .k { color: #66d9ef } /* Keyword */ +.highlight .l { color: #ae81ff } /* Literal */ +.highlight .n { color: #f8f8f2 } /* Name */ +.highlight .o { color: #f92672 } /* Operator */ +.highlight .p { color: #f8f8f2 } /* Punctuation */ +.highlight .ch { color: #75715e } /* Comment.Hashbang */ +.highlight .cm { color: #75715e } /* Comment.Multiline */ +.highlight .cp { color: #75715e } /* Comment.Preproc */ +.highlight .cpf { color: #75715e } /* Comment.PreprocFile */ +.highlight .c1 { color: #75715e } /* Comment.Single */ +.highlight .cs { color: #75715e } /* Comment.Special */ +.highlight .gd { color: #f92672 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gi { color: #a6e22e } /* Generic.Inserted */ +.highlight .go { color: #66d9ef } /* Generic.Output */ +.highlight .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #75715e } /* Generic.Subheading */ +.highlight .kc { color: #66d9ef } /* Keyword.Constant */ +.highlight .kd { color: #66d9ef } /* Keyword.Declaration */ +.highlight .kn { color: #f92672 } /* Keyword.Namespace */ +.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ +.highlight .kr { color: #66d9ef } /* Keyword.Reserved */ +.highlight .kt { color: #66d9ef } /* Keyword.Type */ +.highlight .ld { color: #e6db74 } /* Literal.Date */ +.highlight .m { color: #ae81ff } /* Literal.Number */ +.highlight .s { color: #e6db74 } /* Literal.String */ +.highlight .na { color: #a6e22e } /* Name.Attribute */ +.highlight .nb { color: #f8f8f2 } /* Name.Builtin */ +.highlight .nc { color: #a6e22e } /* Name.Class */ +.highlight .no { color: #66d9ef } /* Name.Constant */ +.highlight .nd { color: #a6e22e } /* Name.Decorator */ +.highlight .ni { color: #f8f8f2 } /* Name.Entity */ +.highlight .ne { color: #a6e22e } /* Name.Exception */ +.highlight .nf { color: #a6e22e } /* Name.Function */ +.highlight .nl { color: #f8f8f2 } /* Name.Label */ +.highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +.highlight .nx { color: #a6e22e } /* Name.Other */ +.highlight .py { color: #f8f8f2 } /* Name.Property */ +.highlight .nt { color: #f92672 } /* Name.Tag */ +.highlight .nv { color: #f8f8f2 } /* Name.Variable */ +.highlight .ow { color: #f92672 } /* Operator.Word */ +.highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ +.highlight .mf { color: #ae81ff } /* Literal.Number.Float */ +.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ +.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ +.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ +.highlight .sa { color: #e6db74 } /* Literal.String.Affix */ +.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ +.highlight .sc { color: #e6db74 } /* Literal.String.Char */ +.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ +.highlight .sd { color: #e6db74 } /* Literal.String.Doc */ +.highlight .s2 { color: #e6db74 } /* Literal.String.Double */ +.highlight .se { color: #ae81ff } /* Literal.String.Escape */ +.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ +.highlight .si { color: #e6db74 } /* Literal.String.Interpol */ +.highlight .sx { color: #e6db74 } /* Literal.String.Other */ +.highlight .sr { color: #e6db74 } /* Literal.String.Regex */ +.highlight .s1 { color: #e6db74 } /* Literal.String.Single */ +.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ +.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #a6e22e } /* Name.Function.Magic */ +.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ +.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ +.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ +.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/zh/1.0/explanation.html b/docs/zh/1.0/explanation.html new file mode 100644 index 0000000..ae9507e --- /dev/null +++ b/docs/zh/1.0/explanation.html @@ -0,0 +1,227 @@ + + + + + + + + 原理说明 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/explanation/async.html b/docs/zh/1.0/explanation/async.html new file mode 100644 index 0000000..2879b95 --- /dev/null +++ b/docs/zh/1.0/explanation/async.html @@ -0,0 +1,272 @@ + + + + + + + + 异步编程基础 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    异步编程基础

    +
    +

    一个经典场景

    +

    假如现在我们想要去创建自己的搜索引擎,为了创建索引数据库,家境贫寒的我们使用了一台单核电脑去爬取网页(I/O型操作),然后再将这些网页内容进行处理(计算型操作)。这个过程如下图所示:

    +../_images/why_single_task.png +

    由于我们有很多网页去要去进行爬取和处理,所以我们只能将上面的过程一个个顺序执行:

    +../_images/why_throughput.png +

    假设每个过程耗时一样,每 1 秒可以进行 2 次这样的处理,那么我可以称我们这台单核的机器的吞吐量为 2 任务/秒。如何提升当前系统的吞吐量?一个比较简单的做法就是增加 CPU 的核心数量:

    +../_images/why_multicore.png +

    如上图所示,我们超级加倍了 CPU 资源后,在未达到网络瓶颈的情况下,吞吐量也轻松翻倍,达到了 4 任务/秒。但是,我们还可以针对单个 CPU 核心增加吞吐量吗?当然可以,使用多线程:

    +../_images/why_multithreading.png +

    桥多麻袋!即便是有用了 2 个线程,我们的单核机器在 2 秒内勉勉强强才完成了 6 个任务,吞吐量也只有 2.7 任务/秒,远比 2 核心机器的 4 任务/秒的吞吐量少诶,所以所线程有啥问题呢?一起康康上面的图:

    +
      +
    • 图片上黄色色块代表额外的时间开销(啥是额外时间?马上就会提到)

    • +
    • 绿绿的色块(代表爬取网页任务)在上图所示的两个线程上可以重叠,代表可以被多个线程一起执行,但是

    • +
    • 非绿色的色块却不能彼此重叠,即不能被多线程同时一起执行。

    • +
    +

    黄色色块代表被上下文切换所消耗的时间,简单理解,就是在单核 CPU 并发处理时在多个线程中切换所消耗的时间。单核 CPU 只能在同一时间处理一件事(假设这个世界上类似超线程技术还没有被发明出来),所以为了在单核 CPU 上同时处理多线程,CPU 必须将自己的时间分成片,并且在这些被分好的时间片内处理一点点当前线程的任务,然后在下一个时间片切换到其他线程继续处理。所以刚才所说的黄色色块所消耗的额外时间实际上就是在这些线程直接来回切换所消耗的时间。虽然上图的黄色色块看上去好像占了好大一块,但实际上没这么夸张,这里是为了方便理解故意搞了个大新闻。

    +

    桥多麻袋!!还没完!既然绿色的色块可以在多线程图上重合,是不是代表这 CPU 同时处理了爬取网页的操作呢?答案是 NO!在绿色的色块覆盖的时间内,CPU 没有处理任何事情,这是因为它正在等待 HTTP 的响应(I/O 操作)。这也是使用了多线程后吞吐量还是提高到了 2.7 任务/秒,而不是降低到 1.7 任务/秒的原因。你可能会想尝试用单核 CPU 搞个多线程去处理计算密集型的任务,但这肯定没有任何提高,甚至会适合其反,正如上图所示的红色色块一样(代表处理爬取到信息的任务;实际中的上下文切换可能更加频繁一些),只能说这些任务看上去好像是同时在运行一样,但是这些任务交替执行所消耗的时间甚至比一个个的顺序执行所消耗的时间更多。这也就是为什么当前的事例中只能被称为并发,而不是并行。

    +

    你也许还会想,每多增加一个线程吞吐量可能还是会有所提升,直到因为上下文切换所消耗的时间过多而影响了系统的吞吐量,这里还没有算上每一个新的线程所占用的系统内存。通常我们不会在实际生产中在单核的 CPU 上去跑它个几百几千个线程。那么问题来了,有可能在单核 CPU 上并发处理数万个 I/O 密集型的任务吗?这就是著名的 C10k 问题,而解决这个问题的办法,正是异步 I/O:

    +../_images/why_coroutine.png +
    +

    注解

    +

    需要注意的是,异步 I/O 和协程是两个概念,但是他们经常被一起提及。在这里,为了简单起见,我们将只讲协程。

    +
    +

    看上去很奈斯嘛!现在的吞吐量是 3.7 任务/秒,已经快达到我们买不起的双核 CPU 机器的 4 任务/秒吞吐量了!虽然这些图都不是使用真是的数据,但是相比系统级别的线程而言,协程真的在上下文切换上开销更加少并且占用更少的内存,这也让协程成为解决 C10K 问题的理想选择,

    +
    +
    +

    协作式多任务处理

    +

    说了半天,协程到底是什么?

    +

    在上一个图中,你可能已经注意到了它和之前一张图片不一样的地方:在同一条线程内,绿色的色块彼此重叠了。这正是因为我们在上图中使用了异步 I/O 编程,而之前的图片中我们使用的是阻塞式的 I/O 编程。顾名思义,阻塞式的 I/O 编程会在代码执行到进行 I/O 操作时等待 I/O 结果返回,从而阻塞当前线程。因此,每条线程中同时只可能执行一个阻塞式的 I/O 操作。为了能够在阻塞式的 I/O 编程中达到并发,要么使用多线程,要么就使用多进程。对比之下,异步 I/O 允许在一条线程中同时进行数千(甚至更多)次 I/O 并发操作,此时在同一线程内,每一次的 I/O 操作只会阻塞当前协程而不是整个线程。和多线程一样,协程可以完成 I/O 的并发,但它仅仅只发生在一条线程内。

    +

    在操作系统中,线程是以抢占式多任务处理的方式进行的。举个栗子,还记得我们之前的图表吗,我们只有一个 CPU 核心,当两个线程准备处理各自爬取到的第一个网页的内容,此时第一个线程先执行,但是还没等线程一执行完毕,操作系统就打断了线程一转而去执行线程二,但是线程一的活还没干完啊,所以还要等到系统挂起线程二重新再执行线程一。如果线程内需要处理的工作很复杂的话,这里面的切换次数那就多了。但也正因为有这样的切换,每个线程才有可能去执行它当前的内容。请阅读下文,并回答文中使用了哪一种修辞手法:

    +
    Thread 1: I wanna run!
    +OS: Okay, here you go...
    +Thread 2: I wanna run!
    +OS: Urh, alright one sec ... Thread 1, hold on for a while!
    +Thread 1: Well I'm not done yet, but you are the boss.
    +OS: It won't be long. Thread 2 it's your turn now.
    +Thread 2: Yay! (&%#$@..+*&#)
    +Thread 1: Can I run now?
    +OS: Just a moment please ... Thread 2, give it a break!
    +Thread 2: Alright ... but I really need the CPU.
    +OS: You'll have it later. Thread 1, hurry up!
    +
    +
    +

    而协程,恰恰相反,它是在某个事件管家的帮助下合作完成任务的。这个事件管家同样也和协程在同一条线程内。但是和通过系统调度完成强制切换的线程不一样,事件管家只会在协程自我挂起的时候完成协程的切换。上面说了线程是抢占式地多任务,因此它是只要自己不阻塞了就想被运行,但是协程相对佛系,一切由事件管家调度,来决定哪一条协程可以被运行。当一条执行中的协程突然遇到一个需要等待的事件(比如HTTP响应),它会将控制权交换给事件管家然后默默等待事件完成,此时事件管家就去找去找另外一条已经得到事件响应的协程,然后将控制权交给它,让它执行。这种并发模式被称为`协作式多任务处理<https://en.wikipedia.org/wiki/Cooperative_multitasking>`_,如果换成拟人的修辞手法,他们大概是这样交流的:

    +
    Coroutine 1: Let me know when event A arrives. I'm done here before that.
    +Event manager: Okay. What about you, coroutine 2?
    +Coroutine 2: Um I've got nothing to do here before event B.
    +Event manager: Cool, I'll be watching.
    +Event manager: (after a while) Hey coroutine 1, event A is here!
    +Coroutine 1: Awesome! Let me see ... looks good, but I need event C now.
    +Event manager: Very well. Seems event B arrived just now, coroutine 2?
    +Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done.
    +Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while.
    +(silence)
    +Event manager: Damn, event C timed out!
    +Coroutine 1: Arrrrh gotta kill myself with an exception :S
    +Event manager: Up to you :/
    +
    +
    +

    协程遇到IO事件将会等待并挂起自己当前的任务,但它不能被其他外部因素打断。所以当有很多协程的时候,并发就是由这些协程时不时地遇到事件时挂起来实现的。当然,如果你写了一个永远不会挂起的协程,它也不会允许任何并发的产生,因为其他的协程根本没有机会去运行。另外一方面,由于不会有多个协程同时运行,我们也大可不必我们所写的担心代码会搞乱一些共享的资源。现在你能理解上图中协程和线程红色色块堆叠方式不一样的原因了吧。

    +
    +

    小技巧

    +

    在Python中,async def 用来声明协程,await 用来将协程加入一个事件循环(即上文提到的将控制权交给事件管家)。

    +
    +
    +
    +

    异步I/O的利弊

    +

    异步I/O能够在同一条线程里面完成成千上万次的并发I/O操作。这样就能够节省多线程带来的CPU上下文切换时间以及内存。因此,在面临I/O密集型的任务时,异步I/O能够有效地使用CPU和内存资源,以达到相对较高的吞吐量。

    +

    并且,在Python中可以轻松自然地编写基于协程的代码。如果业务逻辑十分复杂,基于协程完成异步I/O的代码读起来也是行云流水。

    +

    但是,对于一个任务来说,异步I/O也会降低吞吐量。比如,调用函数 recv() ,它仅仅只是阻塞地等待返回值。而如果想要它变为异步,则必须要注册读事件,等待事件循环,尝试调用 ``recv()``函数,重复以上步骤,直到结果真正返回,然后再进行回调。所以用了协程之后,整个框架的开销实际上更大了。不过幸好有了像uvloop这样优秀的事件管理模块,上述的开销已经被极大地减少。即使是这样,和阻塞式的I/O相比,总提上开销还是大了些。

    +

    想要去为异步I/O的程序计算耗时也是比较费劲的,由于协作的特性,程序的可预测性会变差。举个栗子,你可能希望当前的协程暂停1秒钟。但是此时如果另外一个协程得到了控制权,开始运行,结果计算太久,花费了2秒钟,当我们再次回到刚才的协程时,2秒钟已经实实在在地过了,这么来看,sleep(1) 这个操作已经不能理解为暂停1秒,而应该理解为至少暂停1秒。所以,你应该努力做到让那些不使用 await 进行调用、同步执行的代码尽可能快得执行,让协程真正得“合作”起来。但即使你真的做到了让同步代码快速执行,有时程序的运行候难免还是会不如你所愿,所以要时刻记住这种在执行时间上的不确定性,这样才能方便更好地排查问题。

    +

    最后,异步编程其实是复杂的。要真正写好异步的代码比想象中的难很多,而且异步的代码比同步的代码更加难以调试。尤其是当整个团队都在处理同一段异步代码时,常常会遇到意想不到的问题。因此,这里给一个比较中肯的建议:仅仅在I/O密集型的高并发场景中谨慎使用异步I/O。不是说使用了异步I/O就能够给并发性能带来的巨大提升,它更像一把双刃剑。而且如果要处理一些对时间要求十分严格的任务,请考虑再三!

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/explanation/engine.html b/docs/zh/1.0/explanation/engine.html new file mode 100644 index 0000000..5676d77 --- /dev/null +++ b/docs/zh/1.0/explanation/engine.html @@ -0,0 +1,679 @@ + + + + + + + + 引擎与连接 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    引擎与连接

    +

    GinoEngine is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together:

    +../_images/engine.png +

    Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users.

    +

    During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use.

    +
    +

    注解

    +

    In SQLAlchemy, database drivers are supposed to follow the DB-API standard, +which does not usually provide a pool implementation. Therefore, SQLAlchemy +has its own pool implementation, created directly in engine. This is where +this diagram doesn't fit SQLAlchemy.

    +
    +

    The pool creates raw connections, not the GinoConnection +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries.

    +

    On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use.

    +
    +

    注解

    +

    Another difference to SQLAlchemy here: GINO execution methods always return +final results, while in SQLAlchemy accessing the result may cause further +implicit database accesses. Therefore GINO engine immediately releases the +connection when the execution method on the engine returns, but SQLAlchemy +can only release the connection implicitly when the result data is found +exhausted.

    +

    By immediately releasing a connection, GINO may not release the related raw +connection when the raw connection was reused from another parent +connection. We'll get to this later.

    +
    +

    GINO also supports implicit execution +without having to specify an engine or connection explicitly. This is done by +binding the engine to the db instance, also known as the +MetaData or the Gino instance. +You may possibly bind a GinoConnection instance, but that +is greatly not recommended because it is very much untested.

    +

    At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +db instance on creation, therefore the models can do implicit execution too +if their db has a bind.

    +

    Then let's get to some details.

    +
    +

    Creating Engines

    +

    GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous GinoEngine is +just gino, but only available after gino is imported:

    +
    import gino, sqlalchemy
    +
    +async def main():
    +    e = await sqlalchemy.create_engine('postgresql://...', strategy='gino')
    +    # e is a GinoEngine
    +
    +
    +
    +

    小技巧

    +

    Please read this SQLAlchemy document +to learn about writing database URLs.

    +
    +

    Also the GINO strategy replaces the default driver of dialect postgresql:// +from psycopg2 to asyncpg, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +postgresql+asyncpg://... or just asyncpg://....

    +

    GINO also offers a shortcut as gino.create_engine(), which only sets the +default strategy to gino and does nothing more. So here is an identical +example:

    +
    import gino
    +
    +async def main():
    +    e = await gino.create_engine('postgresql://...')
    +    # e is also a GinoEngine
    +
    +
    +

    As you may have noticed, when using the GINO strategy, +create_engine() returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default.

    +

    For it is just SQLAlchemy create_engine(), the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!):

    +

    For Dialect:

    + +

    For Engine:

    + +

    While these parameters are discarded by GINO:

    + +

    In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from create_pool(). +For example, we can create an engine without initial connections:

    +
    e = await gino.create_engine('postgresql://...', min_size=0)
    +
    +
    +

    Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:

    +
    import sqlalchemy
    +
    +metadata = sqlalchemy.MetaData()
    +metadata.bind = 'postgresql://...'
    +
    +# or in short
    +
    +metadata = sqlalchemy.MetaData('postgresql://...')
    +
    +
    +

    This implicitly calls create_engine() under the hood. However +in GINO, creating an engine requires await, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass Gino, reverted it to simple assignment:

    +
    import gino
    +
    +db = gino.Gino()
    +
    +async def main():
    +    # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind
    +    engine = await gino.create_engine('postgresql://...')
    +    db.bind = engine
    +
    +
    +

    And provided a shortcut to do so:

    +
    engine = await db.set_bind('postgresql://...')
    +
    +
    +

    And another simpler shortcut for one-time usage:

    +
    db = await gino.Gino('postgresql://...')
    +
    +
    +

    To unset a bind and close the engine:

    +
    engine, db.bind = db.bind, None
    +await engine.close()
    +
    +
    +

    Or with a shortcut correspondingly:

    +
    await engine.pop_bind().close()
    +
    +
    +

    Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +
    +
    +

    Managing Connections

    +

    With a GinoEngine at hand, you can acquire connections +from the pool now:

    +
    conn = await engine.acquire()
    +
    +
    +

    Don't forget to release it after use:

    +
    await conn.release()
    +
    +
    +

    Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    Here conn is a GinoConnection instance. As mentioned +previously, GinoConnection is mapped to an underlying raw +connection, as shown in following diagram:

    +../_images/connection.png +

    Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: reuse and +lazy. They are keyword arguments on acquire() +and by default switched off.

    +
    +

    reuse

    +

    When acquiring a GinoConnection (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +GinoConnection (2). This is the default behavior of +acquire() with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:

    +
    async with engine.acquire() as conn1:
    +    async with engine.acquire() as conn2:
    +        # conn2 is a completely different connection than conn1
    +
    +
    +

    But sometimes conn2 may exist in a different method:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner()
    +
    +async def inner():
    +    async with engine.acquire() as conn2:
    +        # ...
    +
    +
    +

    And we probably wish inner could reuse the same raw connection in +outer to save some resource, or borrow a new one if inner is +individually called without outer:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner(conn2=None):
    +    if conn2 is None:
    +        async with engine.acquire() as conn2:
    +            # ...
    +    else:
    +        # the same ... again
    +
    +
    +

    This is exactly the scenario reuse could be useful. We can simply tell the +acquire() to reuse the most recent reusable +connection in current context by setting reuse=True, as presented in this +identical example:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner():
    +    async with engine.acquire(reuse=True) as conn2:
    +        # ...
    +
    +
    +

    Back to previous diagram, the blue GinoConnection +instances (3, 4, 6) are "reusing connections" acquired with reuse=True, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that acquire(reuse=True) always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack.

    +
    +

    小技巧

    +

    By context, we are actually referring to the context concept in +contextvars the +new module in Python 3.7, and its partial backport aiocontextvars. Simply speaking, you may +treat a series of function calls in a chain as in the same context, even if +there is an await. It's something like a thread local in asyncio.

    +
    +

    GinoConnection (2) may be created through +acquire(reuse=True) too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection.

    +

    Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +GinoConnection can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more.

    +
    +
    +

    lazy

    +

    As you may have found, GinoConnection (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set lazy=True on acquire.

    +

    A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, GinoConnection (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +
    +
    +

    On implementation level, lazy is extremely easy in +acquire(): if lazy=False then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, GinoConnection will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all GinoConnection +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +    await conn.release(permanent=False)        # release (8)
    +    await asyncio.sleep(10)                    # simulate long I/O work
    +    await conn.scalar('select now()')          # re-acquire a new raw connection,
    +                                               #   not necessarily the same (8)
    +
    +
    +

    When used together with reuse, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set lazy=False on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again:

    +../_images/connection.png +
    +
    +

    reusable

    +

    Usually, you don't have to worry about the two options reuse and lazy, +using the default acquire() will always create +a concrete GinoConnection with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use reusable=False on acquire. As shown in the diagram, the unreusable +GinoConnection is an orphan away from any stack:

    +
    async with engine.acquire():                    # (2)
    +    async with engine.acquire(reusable=False):  # the unreusable connection
    +        async with engine.acquire(reuse=True):  # (3)
    +
    +
    +

    Unreusable connections can be lazy. But it is usually meaningless to specify +both reuse=True and reusable=False at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +acquire(reuse=True, reusable=False) unless you know what it does.

    +
    +
    +

    current_connection

    +

    Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +current_connection which is always the reusable +GinoConnection at the top of current stack, or None +if current stack is empty.

    +
    +

    小技巧

    +

    The different between current_connection +and acquire(reuse=True) is, the +latter always produces a GinoConnection, while the +former may not.

    +
    +
    +
    +
    +

    Executing Queries

    +

    Once you have a GinoConnection instance, you can start +executing queries with it. There are 6 variants of the execute method: +all(), +first(), +one(), +one_or_none(), +scalar() and +status(). They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results:

    +
      +
    • all() returns all results in a +list, which may be empty when the query has no result, empty but +still a list.

    • +
    • first() returns the first result directly, +or None if there is no result at all. There is usually some optimization +behind the scene to efficiently get only the first result, instead of loading +the full result set into memory.

    • +
    • one() returns exactly one result. If there +is no result at all or if there are multiple results, an exception is raised.

    • +
    • one_or_none() is similar to +one(), but it returns None if there is +no result instead or raising an exception.

    • +
    • scalar() is similar to +first(), it returns the first value of the +first result. Quite convenient to just retrieve a scalar value from database, +like NOW(), MAX(), COUNT() or whatever generates a single value. +None is also returned when there is no result, it is up to you how to +distinguish no result and the first value is NULL.

    • +
    • status() executes the query and discard all +the query results at all. Instead it returns the execution status line as it +is, usually a textual string. Note, there may be no optimization to only +return the status without loading the results, so make your query generate +nothing if you don't want any result.

    • +
    +

    By "result", I meant RowProxy of SQLAlchemy - an +immutable row instance with both tuple and dict interfaces. +Database values are translated twice before they are eventually stored in a +RowProxy: first by the database driver (dialect) +from network payload to Python objects (see Type Conversion of +how asyncpg does this), second by SQLAlchemy +result_processor() depending on the actual +type and dialect.

    +

    The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy execute() (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +multiparams, all 4 methods will always return None discarding all +results. Likewise, the parameter values are processed twice too: first by +bind_processor() then the database driver.

    +

    GINO also supports SQLAlchemy +execution_options() provided either on +engine level, +connection level or on +queries. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling return_model and providing a model will make +all() and +first() return ORM model instance(s) instead +of RowProxy instance(s). See also +execution_options() for more information.

    +

    In addition, GINO has an iterate() method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a server-side cursor.

    +
    +
    +

    Implicit Execution

    +

    Acquire a GinoConnection and execute queries on it, that +is the most explicit way. You can also execute queries on a +GinoEngine instance. In this case, a connection will be +acquired with reuse=True for you implicitly, and released after returning:

    +
    await engine.scalar('select now()')
    +
    +
    +

    Equals to:

    +
    async with engine.acquire(reuse=True) as conn:
    +    await conn.scalar('select now()')
    +
    +
    +

    This allows you to easily write connectionless code. For example:

    +
    async def get_now():
    +    return await engine.scalar('select now()')
    +
    +async def main():
    +    async with engine.acquire():
    +        now = await get_now()
    +        await engine.status('UPDATE ...')
    +
    +
    +

    In this example, main() will take only one raw connection. get_now() +can also work alone out of any acquire() context, thanks to reuse.

    +

    Furthermore, GINO provides the same query APIs on Gino +directly. They are simply delegates to corresponding API methods on the +bind. This allows even engine-less programming:

    +
    db = gino.Gino()
    +
    +async def get_now():
    +    return await db.scalar('select now()')
    +
    +async def main():
    +    async with db.with_bind('postgresql://...'):
    +        now = await get_now()
    +        await db.status('UPDATE ...')
    +
    +
    +
    +

    注解

    +

    In this example we didn't put the two queries in an acquire() block, so +they might be executed in two different connections.

    +
    +

    At last, the SQLAlchemy implicit execution +on queries also work in GINO, under an extension named gino:

    +
    await users_table.select().gino.all()
    +
    +
    +

    By default, the extension GinoExecutor is injected on +Executable as a property of name gino +at the creation of Gino instance. Therefore, any +Executable object has the gino +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the bind of the db instance.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/explanation/why.html b/docs/zh/1.0/explanation/why.html new file mode 100644 index 0000000..7208e03 --- /dev/null +++ b/docs/zh/1.0/explanation/why.html @@ -0,0 +1,407 @@ + + + + + + + + 为什么要用异步 ORM? - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    为什么要用异步 ORM?

    +

    Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly.

    +
    +

    When asyncio Helps

    +

    As Mike Bayer - the author of SQLAlchemy - pointed out, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM".

    +

    The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +异步编程基础. So the ultimate reason to use asyncio should be the application itself, +not the database.

    +

    For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead.

    +../_images/263px-Minimum-Tonne.svg.png +

    Another example is authentication using OpenID Connect Authorization Code Flow as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +Liebig's law, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave.

    +

    Arbitrary delays may happen in the database too. In PostgreSQL, you can LISTEN to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case.

    +

    In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is:

    +
    +
    +

    How to Access Database

    +

    The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here.

    +

    Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:

    +
    import asyncio
    +from fastapi import FastAPI
    +from sqlalchemy import create_engine
    +from sqlalchemy.orm import sessionmaker
    +
    +app = FastAPI()
    +e = create_engine("postgresql://localhost")
    +Session = sessionmaker(bind=e)
    +
    +
    +@app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    This follows advices found in When do I construct a Session, when do I commit it, and when do I close it? from SQLAlchemy - linking +the session scope to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is kill -9.

    +

    What just happened is called a resource starvation. While the first 10 +requests were waiting for the async work (sleep(0.1)), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default max_overflow=10) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition.

    +

    The root cause of such starvation is making asynchronous calls in a transaction +scope created implicitly by the default autocommit=False. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in transaction scopes by explicitly ending them:

    +
    @app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        session.rollback()  # return the connection to avoid starvation
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    Because of the implicit nature of typical ORMs, it's very hard to identify all such +transaction scopes and make sure there's no await in them - you may have started +an implicit transaction by simply accessing a property like current_user.name. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this.

    +

    What about thread pool?

    +

    The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool.

    +

    This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design.

    +

    In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs.

    +
    +
    +

    Asynchronous ORM Done Right

    +

    I would describe a proper asynchronous ORM as follows:

    +
    +

    Don't Starve.

    +

    The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything async.

    +

    In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:

    +
    @app.get("/")
    +async def read_root():
    +    async with db.acquire() as conn:  # }
    +        async with conn.begin():      # } These two won't block the main thread
    +            now = await db.scalar("SELECT now()")
    +            await asyncio.sleep(0.1)  # do some async work
    +            return str(now)
    +
    +
    +

    Even though this code won't cause any resource starvation, using await within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages:

    +
      +
    1. As the database connection pool is usually much smaller than the asynchronous server +concurrency, this kind of code caps the concurrency down to the DB pool level.

    2. +
    3. Taking a database connection from the pool for nothing is a waste of resource, +especially when the database pool is the shortest stave.

    4. +
    5. Database transactions should be kept short as much as possible. Because long-hanging +transactions may keep certain database locks, leading to performance issues or even +triggering a chain reaction of deadlocks.

    6. +
    +
    +
    +

    Be explicit.

    +

    With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an await or async with, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following the pattern of Twisted, asyncio is already forcing +explicit await for any asynchronous operations.

    +

    Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example:

    +
      +
    • We could design the ORM model instances to be stateless, so that the users don't have +to learn and worry about maintaining the state of the instances.

    • +
    • There shouldn't be any "buffered operations" which users could "flush" with a single +statement once for all.

    • +
    • The user should just give direct one-off commands and the ORM executes them right away.

    • +
    • Also I think the convenience tooling should be well-balanced, the user doesn't have to +guess or remember what an API means - for example, there're more than one ways to load +a many-to-one relationship, I'd prefer to write the query by myself rather than trying +to remember what "join_without_n_plus_1()" means.

    • +
    +
    +
    +

    Be productive.

    +

    Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two?

    +

    I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design.

    +

    Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again.

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/genindex.html b/docs/zh/1.0/genindex.html new file mode 100644 index 0000000..270c3b2 --- /dev/null +++ b/docs/zh/1.0/genindex.html @@ -0,0 +1 @@ +

    genindex.html

    \ No newline at end of file diff --git a/docs/zh/1.0/how-to.html b/docs/zh/1.0/how-to.html new file mode 100644 index 0000000..06dc8d9 --- /dev/null +++ b/docs/zh/1.0/how-to.html @@ -0,0 +1,274 @@ + + + + + + + + 进阶用法 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    + + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/alembic.html b/docs/zh/1.0/how-to/alembic.html new file mode 100644 index 0000000..4479cea --- /dev/null +++ b/docs/zh/1.0/how-to/alembic.html @@ -0,0 +1,294 @@ + + + + + + + + 使用 Alembic - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    使用 Alembic

    +

    Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO.

    +

    To add migrations to project first of all, add alembic as dependency:

    +
    $ pip install --user alembic
    +
    +
    +

    When you need to set up alembic for your project.

    +

    Prepare sample project. We will have a structure:

    +
    alembic_sample/
    +    my_app/
    +        models.py
    +
    +
    +

    Inside models.py define simple DB Model with GINO:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +
    +

    Set up Alembic

    +

    This will need to be done only once. Go to the main folder of your project +alembic_sample and run:

    +
    $ alembic init alembic
    +
    +
    +

    Alembic will create a bunch of files and folders in your project directory. One of them +will be alembic.ini. Open alembic.ini (you can find it in the main project +folder alembic_sample). Now change property sqlalchemy.url = with your DB +credentials. Like this:

    +
    sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    +
    +
    +

    Next go to folder alembic/ and open env.py file. Inside the env.py file you +need to import the db object. In our case db object is db from models +modules. This is a variable that links to your Gino() instance.

    +

    Inside alembic/env.py:

    +
    from main_app.models import db
    +
    +
    +

    And change target_metadata = to:

    +
    target_metadata = db
    +
    +
    +

    That’s it. We finished setting up Alembic for a project.

    +
    +

    注解

    +

    All alembic commands must be run always from the folder that contains the +alembic.ini file.

    +
    +
    +
    +

    Create first migration revision

    +

    Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema.

    +
    $ alembic revision -m "first migration" --autogenerate --head head
    +
    +
    +

    If you have any problems relative to package imports similar to this example:

    +
    File "alembic/env.py", line 7, in <module>
    +    from main_app.models import db
    +ModuleNotFoundError: No module named 'main_app'
    +
    +
    +

    Either install your project locally with pip install -e ., poetry install or +python setup.py develop, or add you package to PYTHONPATH, like this:

    +
    $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample
    +
    +
    +

    After the successful run of alembic revision in folder alembic/versions you will +see a file with new migration.

    +
    +
    +

    Apply migration on DB

    +

    Now time to apply migration to DB. It will create tables based on you DB Models.

    +
    $ alembic upgrade head
    +
    +
    +

    Great. Now you apply your first migration. Congratulations!

    +

    Next time, when you will make any changes in DB models just do:

    +
    $ alembic revision -m "your migration description" --autogenerate --head head
    +
    +
    +

    And

    +
    alembic upgrade head
    +
    +
    +

    Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/contributing.html b/docs/zh/1.0/how-to/contributing.html new file mode 100644 index 0000000..311193e --- /dev/null +++ b/docs/zh/1.0/how-to/contributing.html @@ -0,0 +1,353 @@ + + + + + + + + 贡献 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    贡献

    +

    Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given.

    +

    您可以通过多种方式做出贡献:

    +
    +

    Types of Contributions

    +
    +

    报告错误

    +

    Report bugs at https://github.com/python-gino/gino/issues.

    +

    如果您要报告错误,请包括:

    +
      +
    • 您的操作系统名称和版本。

    • +
    • 有关本地设置的任何详细信息都可能有助于排除故障。

    • +
    • 重现错误的详细步骤。

    • +
    +
    +
    +

    修复错误

    +

    在GitHub issues 查找错误。任何人都可以将标记为“bug”和“help wanted”并且是打开状态的问题修复。

    +
    +
    +

    实现功能

    +

    在GitHub issues 查找功能。任何人都可以将标记为“增强”和“需要帮助”的打开状态的需求实现。

    +
    +
    +

    写文档

    +

    GINO需要更多的使用文档,无论是作为官方GINO文档的一部分,还是文档字符串,甚至是博客,文章等等。

    +
    +
    +

    提交反馈

    +

    The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues.

    +

    如果您要提出一项功能:

    +
      +
    • 详细解释它是如何工作的。

    • +
    • 为了更容易实现,请保持范围尽可能窄。

    • +
    • Remember that this is a volunteer-driven project, and that contributions +are welcome :)

    • +
    +
    +
    +
    +

    Get Started!

    +

    Ready to contribute? Here's how to set up gino for local development.

    +
      +
    1. Fork the gino repo on GitHub.

    2. +
    3. Clone your fork locally:

      +
      $ git clone git@github.com:your_name_here/gino.git
      +
      +
      +
    4. +
    5. Create a branch for local development:

      +
      $ cd gino/
      +$ git checkout -b name-of-your-bugfix-or-feature
      +
      +
      +
    6. +
    +

    Now you can make your changes locally.

    +
      +
    1. Create virtual environment. Example for virtualenvwrapper:

      +
      $ mkvirtualenv gino
      +
      +
      +
    2. +
    3. Activate the environment and install requirements:

      +
      $ pip install -r requirements_dev.txt
      +
      +
      +
    4. +
    5. When you're done making changes, check that your changes pass syntax checks:

      +
      $ flake8 gino tests
      +
      +
      +
    6. +
    +

    7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):

    +
    $ pytest tests
    +$ tox
    +
    +
    +
      +
    1. For docs run:

      +
      $ make docs
      +
      +
      +
    2. +
    +

    It will build and open up docs in your browser.

    +
      +
    1. Commit your changes and push your branch to GitHub:

      +
      $ git add .
      +$ git commit -m "Your detailed description of your changes."
      +$ git push origin name-of-your-bugfix-or-feature
      +
      +
      +
    2. +
    3. Submit a pull request through the GitHub website.

    4. +
    +
    +
    +

    Pull Request Guidelines

    +

    Before you submit a pull request, check that it meets these guidelines:

    +
      +
    1. The pull request should include tests.

    2. +
    3. If the pull request adds functionality, the docs should be updated. Put +your new functionality into a function with a docstring, and add the +feature to the list in README.rst.

    4. +
    5. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check +https://github.com/python-gino/gino/actions?query=event%3Apull_request +and make sure that the tests pass for all supported Python versions.

    6. +
    +
    +
    +

    Tips

    +

    To run a subset of tests:

    +
    $ py.test -svx tests.test_gino
    +
    +
    +

    By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:

    +
    CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino';
    +CREATE DATABASE gino WITH OWNER = gino;
    +
    +
    +

    Then run the tests like so:

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino
    +$ py.test
    +
    +
    +

    Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):

    +
    $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
    +$ openssl rsa -in privkey.pem -passin pass:abcd -out server.key
    +$ openssl req -x509 -in server.req -text -key server.key -out server.crt
    +$ chmod 600 server.key
    +$ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    +
    +
    +

    Terminal 2 (client):

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433
    +$ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'"
    +$ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;"
    +$ pytest tests/test_aiohttp.py
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/crud.html b/docs/zh/1.0/how-to/crud.html new file mode 100644 index 0000000..1edd480 --- /dev/null +++ b/docs/zh/1.0/how-to/crud.html @@ -0,0 +1,192 @@ + + + + + + + + 增删改查 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    增删改查

    +

    未完待续

    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/faq.html b/docs/zh/1.0/how-to/faq.html new file mode 100644 index 0000000..e376e6f --- /dev/null +++ b/docs/zh/1.0/how-to/faq.html @@ -0,0 +1,555 @@ + + + + + + + + 常见问题 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    常见问题

    +
    +

    SQLAlchemy 1.4 supports asyncio, what will GINO be?

    +

    Starting from 1.4, SQLAlchemy will support asyncio. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +greenlet. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed.

    +

    Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features:

    +
      +
    • Contextual Connections (see 引擎与连接)

    • +
    • SQLAlchemy Core-based CRUD models

    • +
    • The GINO Loader system

    • +
    • Async MySQL support

    • +
    • Typing support NEW

    • +
    • Trio support NEW

    • +
    • Execution performance NEW

    • +
    +

    As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized.

    +
    +
    +

    ORM or not ORM?

    +

    GINO does perform the Object-Relational Mapping work under the +Data Mapper Pattern, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely non-ORM way.

    +
    +
    +

    Can I use features of SQLAlchemy ORM?

    +

    SQLAlchemy has two parts:

    +
      +
    • SQLAlchemy Core

    • +
    • SQLAlchemy ORM

    • +
    +

    GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO.

    +
    +
    +

    How to join?

    +

    GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know how to write join in +SQLAlchemy. +Especially, Ádám made some amazing upgrades in +GINO #113 to make join easier, so +that you can use model classes directly as if they are tables in joining:

    +
    results = await User.join(Book).select().gino.all()
    +
    +
    +
    +
    +

    How to connect to database through SSL?

    +

    It depends on the dialect and database driver. For asyncpg, keyword arguments +on asyncpg.connect() are directly +available on create_engine() or db.set_bind(). Therefore, enabling SSL is rather easy:

    +
    engine = await gino.create_engine(..., ssl=True)
    +
    +
    +
    +
    +

    What is aiocontextvars and what does it do?

    +

    It is a partial backport of the new built-in module contextvars introduced in Python +3.7. In Python 3.5 and 3.6, aiocontextvars patches loop.create_task() +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2

    +

    If you are using Python 3.7, then aiocontextvars does nothing at all.

    +
    +

    注解

    +

    This answer is for GINO 0.8 and later, please check earlier versions of +this documentation if you are using GINO 0.7.

    +
    +
    +
    +

    How to define relationships?

    +

    GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see 加载器与关系 for more information.

    +
    +
    +

    How to define index with multiple columns?

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    first_name = db.Column(db.Unicode())
    +    last_name = db.Column(db.Unicode())
    +
    +    _name_idx = db.Index('index_on_name', 'first_name', 'last_name')
    +
    +
    +

    The _name_idx is not used.

    +
    +
    +

    Is there a django admin interface for GINO?

    +

    Not quite yet, please follow this discussion.

    +
    +
    +

    How to use multiple databases for different users on the fly?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query.

    +

    In order to use multiple databases, you would need multiple +GinoEngine instances. Here's a full example using FastAPI with +lazy engine creation:

    +
    from asyncio import Future
    +from contextvars import ContextVar
    +
    +from fastapi import FastAPI, Request
    +from gino import create_engine
    +from gino.ext.starlette import Gino
    +
    +engines = {}
    +dbname = ContextVar("dbname")
    +
    +
    +class ContextualGino(Gino):
    +    @property
    +    def bind(self):
    +        e = engines.get(dbname.get(""))
    +        if e and e.done():
    +            return e.result()
    +        else:
    +            return self._bind
    +
    +    @bind.setter
    +    def bind(self, val):
    +        self._bind = val
    +
    +
    +app = FastAPI()
    +db = ContextualGino(app)
    +
    +
    +@app.middleware("http")
    +async def lazy_engines(request: Request, call_next):
    +    name = request.query_params.get("db", "postgres")
    +    fut = engines.get(name)
    +    if fut is None:
    +        fut = engines[name] = Future()
    +        try:
    +            engine = await create_engine("postgresql://localhost/" + name)
    +        except Exception as e:
    +            fut.set_exception(e)
    +            del engines[name]
    +            raise
    +        else:
    +            fut.set_result(engine)
    +    await fut
    +    dbname.set(name)
    +    return await call_next(request)
    +
    +
    +@app.get("/")
    +async def get():
    +    return dict(dbname=await db.scalar("SELECT current_database()"))
    +
    +
    +
    +
    +

    How to load complex query results?

    +

    The API doc of gino.loader explains the available loaders, and there're a few +examples in 加载器与关系 too.

    +

    Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a TupleLoader with two sub-loaders - +ModelLoader and ColumnLoader:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Be ware of the tuple in .gino.load((...)).

    +
    +
    +

    How to do bulk or batch insert / update?

    +

    For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:

    +
    new_names = ["Austin", "Ali", "Jeff", "Marissa"]
    +
    +
    +

    To quickly insert the names in one query, first construct a dict with the +{"model_key": "value"} format:

    +
    new_names_dict = [dict(name=new_name) for new_name in new_names]
    +>> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}]
    +
    +
    +

    Finally, run an insert statement on the model:

    +
    await User.insert().gino.all(new_names_dict)
    +
    +
    +
    +
    +

    How to print the executed SQL?

    +

    GINO uses the same approach from SQLAlchemy: create_engine(..., echo=True). +(Or db.set_bind(..., echo=True)) Please see also here.

    +

    If you use any extension, you can also set that in config, by db_echo or DB_ECHO.

    +
    +
    +

    How to run EXISTS SQL?

    +
    await db.scalar(db.exists().where(User.email == email).select())
    +
    +
    +
    +
    +

    How to work with Alembic?

    +

    The fact that Gino is a MetaData is the +key to use Alembic. Just import and set target_metadata = db in Alembic env.py +will do. See 使用 Alembic for more details.

    +
    +
    +

    How to join the same table twice?

    +

    This is the same pattern as described in SQLAlchemy Adjacency List Relationships, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +alias(), for example:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.ForeignKey("users.id"))
    +
    +Parent = User.alias()
    +query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
    +users = await query.gino.load(User.load(parent=Parent)).all()
    +
    +
    +
    +
    +

    How to execute raw SQL with parameters?

    +

    Wrap the SQL with text(), and use keyword arguments:

    +
    query = db.text('SELECT * FROM users WHERE id = :id_val')
    +row = await db.first(query, id_val=1)
    +
    +
    +

    You may even load the rows into model instances:

    +
    query = query.execution_options(loader=User)
    +user = await db.first(query, id_val=1)
    +
    +
    +
    +
    +

    Gino engine is not initialized?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query. If bind is not set before +this, you'll see this error:

    +
    gino.exceptions.UninitializedError: Gino engine is not initialized.
    +
    +
    +

    You could use either:

    +
      +
    • Call set_bind() or with_bind() to set the +bind on the Gino instance.

    • +
    • Use one of the Web framework extensions to set the bind for you in usually the server +start-up hook.

    • +
    • Use explicit bind for each execution, for example:

      +
      engine = await create_engine("...")
      +# ...
      +user = await User.get(request.user_id, bind=engine)
      +
      +
      +
    • +
    +
    +
    +

    How can I do SQL xxxx in GINO?

    +

    GINO uses SQLAlchemy Core queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy Table instances, and the column +attributes on GINO model classes are just SQLAlchemy Column +instances, you can use them in building your SQLAlchemy Core queries.

    +

    Alternatively, you could always execute the raw SQL directly, see How to execute raw SQL with parameters? above.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/json-props.html b/docs/zh/1.0/how-to/json-props.html new file mode 100644 index 0000000..0e23326 --- /dev/null +++ b/docs/zh/1.0/how-to/json-props.html @@ -0,0 +1,373 @@ + + + + + + + + JSON 扩展属性 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    JSON 扩展属性

    +

    GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields.

    +
    +

    Quick Start

    +
    from gino import Gino
    +from sqlalchemy.dialects.postgresql import JSONB
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +    birthday = db.DateTimeProperty()
    +
    +
    +

    The age and birthday are JSON properties stored in the profile column. You +may use them the same way as a normal GINO model field:

    +
    u = await User.create(name="daisy", age=18)
    +print(u.name, u.age)  # daisy 18
    +
    +
    +
    +

    注解

    +

    profile is the default column name for all JSON properties in a model. If you +need a different column name for some JSON properties, you'll need to specify +explicitly:

    +
    audit_profile = db.Column(JSON, nullable=False, server_default="{}")
    +
    +access_log = db.ArrayProperty(prop_name="audit_profile")
    +abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
    +
    +
    +
    +

    Using JSON properties in queries is supported:

    +
    await User.query.where(User.age > 16).gino.all()
    +
    +
    +

    This is simply translated into a native JSON query like this:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
    +
    +
    +

    Datetime type is very much the same:

    +
    from datetime import datetime
    +
    +await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
    +
    +
    +

    And the generated SQL:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
    +-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
    +
    +
    +

    Here's a list of all the supported JSON properties:

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    JSON 扩展属性

    Python type

    JSON type

    Database Type

    StringProperty

    str

    string

    text

    IntegerProperty

    int

    number

    int

    BooleanProperty

    bool

    boolean

    boolean

    DateTimeProperty

    datetime

    string

    text

    ObjectProperty

    dict

    object

    JSON

    ArrayProperty

    list

    array

    JSON

    +
    +
    +

    Hooks

    +

    JSON property provides 2 instance-level hooks to customize the data:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @age.before_set
    +    def age(self, val):
    +        return val - 1
    +
    +    @age.after_get
    +    def age(self, val):
    +        return val + 1
    +
    +u = await User.create(name="daisy", age=18)
    +print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
    +
    +
    +

    And 1 class-level hook to customize the SQLAlchemy expression of the property:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    height = db.JSONProperty()
    +
    +    @height.expression
    +    def height(cls, exp):
    +        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
    +
    +
    +
    +
    +

    Create Index on JSON Properties

    +

    We'll need to use declared_attr() to wait until the model class +is initialized. The rest is very much the same as defining a usual index:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @db.declared_attr
    +    def age_idx(cls):
    +        return db.Index("age_idx", cls.age)
    +
    +
    +

    This will lead to the SQL below executed if you run db.gino.create_all():

    +
    CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
    +
    +
    +
    +

    警告

    +

    Alembic doesn't support auto-generating revisions for functional indexes yet. You'll +need to manually edit the revision. Please follow this issue for updates.

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/loaders.html b/docs/zh/1.0/how-to/loaders.html new file mode 100644 index 0000000..aa7b74c --- /dev/null +++ b/docs/zh/1.0/how-to/loaders.html @@ -0,0 +1,636 @@ + + + + + + + + 加载器与关系 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    加载器与关系

    +

    Loaders are used to load database row results into objects.

    +

    GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined.

    +
    +

    Model Loader

    +

    The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:

    +
    query = db.select([User])
    +rows = await query.gino.all()
    +
    +
    +

    In order to load rows into User objects, you can provide an execution +option loader with a new ModelLoader instance:

    +
    from gino.loader import ModelLoader
    +
    +query = db.select([User])
    +query = query.execution_options(loader=ModelLoader(User))
    +users = await query.gino.all()
    +
    +
    +

    The ModelLoader would then load each database row into a +User object. As this is frequently used, GINO made it a shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User.load())
    +users = await query.gino.all()
    +
    +
    +

    And another shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User)
    +users = await query.gino.all()
    +
    +
    +
    +

    小技巧

    +

    User as loader is transformed into ModelLoader(User) by +Loader.get(), explained later in "Loader +Expression".

    +
    +

    And again:

    +
    query = db.select([User])
    +users = await query.gino.load(User).all()
    +
    +
    +

    This is identical to the normal CRUD query:

    +
    users = await User.query.gino.all()
    +
    +
    +
    +
    +

    Loader Expression

    +

    So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than ModelLoader, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders.

    +

    Here is an example using all loaders at once:

    +
    uid, user, sep, cols = await db.select([User]).gino.load(
    +    (
    +        User.id,
    +        User,
    +        '|',
    +        lambda row, ctx: len(row),
    +    )
    +).first()
    +
    +
    +

    Let's check this piece by piece. Overall, the argument of +load() is a tuple. This is interpreted into a +TupleLoader, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a TupleLoader is a tuple.

    +

    Column in Loader Expressions are interpreted as +ColumnLoader. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, ColumnLoader uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use ColumnLoader, +you'll have to declare columns for the query:

    +
    now = db.Column('time', db.DateTime())
    +result = await db.first(db.text(
    +    'SELECT now() AT TIME ZONE \'UTC\''
    +).columns(
    +    now,
    +).gino.load(
    +    ('now:', now)
    +).query)
    +print(*result)  # now: 2018-04-08 08:23:02.431847
    +
    +
    +

    Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +ModelLoader. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values.

    +
    +

    小技巧

    +

    For a complex loader expression, the same row is given to all loaders, so +it doesn't matter User.id is already used before the model loader.

    +
    +

    The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +map(), the return value of the call will be the loaded result.

    +

    At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the '|' separator, it will show up as the third item +in every result returned by the query.

    +
    +
    +

    Many-to-One Relationship

    +

    A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +

    So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:

    +
    async for child in Child.load(parent=Parent).gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    As you may have noticed, Child.load is exactly the shortcut to create +ModelLoader in the very first example. With some +additional keyword arguments, Child.load(parent=Parent) is still a +ModelLoader for Child, the model loader is at the +same time a query builder. It is identical to do this:

    +
    async for child in Child.load(parent=Parent).query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    The query dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The Loader simply forwarded unknown +attributes to its query, that's why .query can +be omitted.

    +

    For ModelLoader, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using setattr(). For example, Parent is +interpreted as ModelLoader(Parent) which loads Parent instances, and +Parent instances are set as the parent attribute of the outer Child +instance.

    +
    +

    警告

    +

    If multiple children references the same parent, then each child owns a +unique parent instance with identical values.

    +
    +
    +

    小技巧

    +

    You don't have to define parent attribute on Child. But if you do, +you gain the ability to customize how parent is stored or retrieved. For +example, let's store the parent instance as _parent:

    +
    class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    _parent = None
    +
    +    @property
    +    def parent(self):
    +        return self._parent
    +
    +    @parent.setter
    +    def parent(self, value):
    +        self._parent = value
    +
    +
    +
    +

    The query builder works recursively. For ModelLoader, it +uses LEFT OUTER JOIN to connect the FROM clauses, in order to achieve +many-to-one scenario. The ON clause is determined automatically by foreign +keys. You can also customize the ON clause in case there is no foreign key +(a promise is a promise):

    +
    loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +async for child in loader.query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    And subloaders can be nested:

    +
    subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
    +
    +
    +

    By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values.

    +
    +
    +

    Self Referencing

    +
    +

    警告

    +

    Experimental feature.

    +
    +

    Self referencing is usually used to create a tree-like structure. For example:

    +
    class Category(db.Model):
    +    __tablename__ = 'categories'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    +
    +
    +

    In order to load leaf categories with their parents, an alias is needed:

    +
    Parent = Category.alias()
    +
    +
    +

    Then the query would be something like this:

    +
    parents = db.select([Category.parent_id])
    +query = Category.load(parent=Parent.on(
    +    Category.parent_id == Parent.id
    +)).where(
    +    ~Category.id.in_(db.select([Category.alias().parent_id]))
    +)
    +async for c in query.gino.iterate():
    +    print(f'Leaf: {c.id}, Parent: {c.parent.id}')
    +
    +
    +

    The generated SQL looks like this:

    +
    SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id
    +  FROM categories LEFT OUTER JOIN categories AS categories_1
    +    ON categories.parent_id = categories_1.id
    + WHERE categories.id NOT IN (
    +           SELECT categories_2.parent_id
    +             FROM categories AS categories_2
    +       )
    +
    +
    +
    +
    +

    Other Relationships

    +

    GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    @children.setter
    +    def add_child(self, child):
    +        self._children.add(child)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child)).all()
    +
    +
    +

    Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the add_child setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +parent.add_child = new_child.

    +

    Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries.

    +

    GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._child = None
    +
    +    @property
    +    def child(self):
    +        return self._child
    +
    +    @child.setter
    +    def child(self, child):
    +        self._child = child
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
    +
    +
    +

    Similarly, you can build many-to-many relationships in the same way:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    def add_child(self, child):
    +        self._children.add(child)
    +        child._parents.add(self)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._parents = set()
    +
    +    @property
    +    def parents(self):
    +        return self._parents
    +
    +
    +class ParentXChild(db.Model):
    +    __tablename__ = 'parents_x_children'
    +
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
    +
    +
    +query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
    +
    +
    +

    Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ParentXChild instances.

    +
    +
    +

    Advanced Usage of Loaders

    +

    You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For example, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Using alias to get ID-ascending pairs from the same table:

    +
    ua1 = User.alias()
    +ua2 = User.alias()
    +join_query = select([ua1, ua2]).where(ua1.id < ua2.id)
    +loader = ua1.load('id'), ua2.load('id')
    +result = await join_query.gino.load(loader).all()
    +print(result)  # e.g. [(1, 2), (1, 3), (2, 3)]
    +
    +
    +

    Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/pool.html b/docs/zh/1.0/how-to/pool.html new file mode 100644 index 0000000..c7bb1b4 --- /dev/null +++ b/docs/zh/1.0/how-to/pool.html @@ -0,0 +1,214 @@ + + + + + + + + 连接池 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    连接池

    +

    Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +NullPool), and users can define their own pools. +The base class should be Pool.

    +

    To use non-default pools in raw GINO:

    +
    from gino.dialects.asyncpg import NullPool
    +create_engine('postgresql://...', pool_class=NullPool)
    +
    +
    +

    To use non-default pools in extensions (taking Sanic as an example):

    +
    from gino.dialects.asyncpg import NullPool
    +from gino.ext.sanic import Gino
    +
    +app = sanic.Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_KWARGS = dict(
    +    pool_class=NullPool,
    +)
    +db = Gino()
    +db.init_app(app)
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/schema.html b/docs/zh/1.0/how-to/schema.html new file mode 100644 index 0000000..d2bfa87 --- /dev/null +++ b/docs/zh/1.0/how-to/schema.html @@ -0,0 +1,416 @@ + + + + + + + + 表结构定义 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    表结构定义

    +

    There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy Table.

    +
    +

    GINO Engine

    +

    This is the minimized way to use GINO - using only +GinoEngine (and GinoConnection +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two.

    +

    For example, the table declaration is the same as SQLAlchemy core tutorial:

    +
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    +
    +metadata = MetaData()
    +
    +users = Table(
    +    'users', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('name', String),
    +    Column('fullname', String),
    +)
    +
    +addresses = Table(
    +    'addresses', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('user_id', None, ForeignKey('users.id')),
    +    Column('email_address', String, nullable=False)
    +)
    +
    +
    +
    +

    注解

    +

    When using GINO Engine only, it is usually your own business to create the +tables with either create_all() on a +normal non-async SQLAlchemy engine, or using Alembic. However it is still +possible to be done with GINO if it had to:

    +
    import gino
    +from gino.schema import GinoSchemaVisitor
    +
    +async def main():
    +    engine = await gino.create_engine('postgresql://...')
    +    await GinoSchemaVisitor(metadata).create_all(engine)
    +
    +
    +
    +

    Then, construct queries, in SQLAlchemy core too:

    +
    ins = users.insert().values(name='jack', fullname='Jack Jones')
    +
    +
    +

    So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:

    +
    async def main():
    +    engine = await gino.create_engine('postgresql://localhost/gino')
    +    conn = await engine.acquire()
    +    await conn.status(ins)
    +    print(await conn.all(users.select()))
    +    # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Here create_engine() creates a GinoEngine, +then acquire() checks out a +GinoConnection, and +status() executes the insert and returns the +status text. This works similarly as SQLAlchemy +execute() - they take the same parameters +but return a bit differently. There are also other similar query APIs:

    + +

    Please go to their API for more information.

    +
    +
    +

    GINO Core

    +

    In previous scenario, GinoEngine must not be set to +metadata.bind because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of MetaData as Gino, +usually instantiated globally under the name of db. It can be used as a +normal MetaData still offering some conveniences:

    +
      +
    • It delegates most public types you can access on sqlalchemy

    • +
    • It works with both normal SQLAlchemy engine and asynchronous GINO engine

    • +
    • It exposes all query APIs on GinoConnection level

    • +
    • It injects two gino extensions on SQLAlchemy query clauses and schema +items, allowing short inline execution like users.select().gino.all()

    • +
    • It is also the entry for the third scenario, see later

    • +
    +

    Then we can achieve previous scenario with less code like this:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +users = db.Table(
    +    'users', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('name', db.String),
    +    db.Column('fullname', db.String),
    +)
    +
    +addresses = db.Table(
    +    'addresses', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('user_id', None, db.ForeignKey('users.id')),
    +    db.Column('email_address', db.String, nullable=False)
    +)
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await users.insert().values(
    +            name='jack',
    +            fullname='Jack Jones',
    +        ).gino.status()
    +        print(await users.select().gino.all())
    +        # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but sqlalchemy seems +never imported. This is useful when ORM is unwanted.

    +
    +

    小技巧

    +

    asyncpgsa does the same thing, +but in a conceptually reversed way - instead of having asyncpg work for +SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that +way too because GINO is inspired by asyncpgsa). Either way works fine, it's +just a matter of taste of whose API style to use, SQLAlchemy or asyncpg.

    +
    +
    +
    +

    GINO ORM

    +

    If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    fullname = db.Column(db.String)
    +
    +
    +class Address(db.Model):
    +    __tablename__ = 'addresses'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    user_id = db.Column(None, db.ForeignKey('users.id'))
    +    email_address = db.Column(db.String, nullable=False)
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await User.create(name='jack', fullname='Jack Jones')
    +        print(await User.query.gino.all())
    +        # Outputs: [<User object at 0x10a8ba860>]
    +
    +
    +
    +

    重要

    +

    The __tablename__ is a mandatory field to define a concrete model.

    +
    +

    As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in db. The class style is just +more declarative. Instead of users.c.name, you can now access the column by +User.name. The implicitly created Table is +available at User.__table__ and Address.__table__. You can use anything +that works in GINO core here.

    +
    +

    注解

    +

    Column names can be different as a class property and database column. +For example, name can be declared as +nickname = db.Column('name', db.Unicode(), default='noname'). In this +example, User.nickname is used to access the column, while in database, +the column name is name.

    +

    What's worth mentioning is where raw SQL statements are used, or +TableClause is involved, like User.insert(), the original name is +required to be used, because in this case, GINO has no knowledge about the +mappings.

    +
    +
    +

    小技巧

    +

    db.Model is a dynamically created parent class for your models. It is +associated with the db on initialization, therefore the table is put in +the very db when you declare your model class.

    +
    +

    Things become different when it comes to CRUD. You can use model level methods +to directly create() a model instance, instead of +inserting a new row. Or delete() a model instance +without needing to specify the where clause manually. Query returns model +instances instead of RowProxy, and row values are +directly available as attributes on model instances. See also: +增删改查.

    +

    After all, GinoEngine is always in use. Next let's dig +more into it.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/how-to/transaction.html b/docs/zh/1.0/how-to/transaction.html new file mode 100644 index 0000000..f61f0b9 --- /dev/null +++ b/docs/zh/1.0/how-to/transaction.html @@ -0,0 +1,300 @@ + + + + + + + + 数据库事务 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    数据库事务

    +

    It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an await will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it.

    +
    +

    Basic usage

    +

    Transactions belong to GinoConnection. The most common +way to use transactions is through an async with statement:

    +
    async with connection.transaction() as tx:
    +    await connection.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    This guarantees a transaction is opened when entering the async with block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at raw_transaction, but in +most cases you don't need to touch it.

    +

    GINO provides two convenient shortcuts to end the transaction early:

    + +

    They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the async with block after +raise_commit() or +raise_rollback() is skipped. The +internal exception is inherited from BaseException so that normal try +... except Exception block can't trap it. This exception stops propagating at +the end of async with block, so you don't need to worry about handling it.

    +

    Transactions can also be started on a GinoEngine:

    +
    async with engine.transaction() as tx:
    +    await engine.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    Here a GinoConnection is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The GinoConnection instance is accessible at +tx.connection. Other than +that, everything else is the same.

    +
    +

    重要

    +

    The implicit connection is by default borrowed with reuse=True. That +means using transaction() of +GinoEngine within a connection context is the same as +calling transaction() of the current +connection without having to reference it, no separate connection shall be +created.

    +
    +

    Similarly, if your Gino instance has a bind, you may also do +the same on it:

    +
    async with db.transaction() as tx:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +
    +
    +

    Nested Transactions

    +

    Transactions can be nested, nested transaction will create a savepoint as for +now on asyncpg. A similar example from asyncpg doc:

    +
    async with connection.transaction() as tx1:
    +    await connection.status('CREATE TABLE mytab (a int)')
    +
    +    # Create a nested transaction:
    +    async with connection.transaction() as tx2:
    +        await connection.status('INSERT INTO mytab (a) VALUES (1), (2)')
    +        # Rollback the nested transaction:
    +        tx2.raise_rollback()
    +
    +    # Because the nested transaction was rolled back, there
    +    # will be nothing in `mytab`.
    +    assert await connection.all('SELECT a FROM mytab') == []
    +
    +
    +

    As you can see, the raise_rollback() +breaks only the async with block of the specified tx2, the outer +transaction tx1 just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate.

    +

    Because of the default reusing behavior, transactions on engine or db +follows the same nesting rules. Please see +GinoTransaction for more information.

    +
    +
    +

    Manual Control

    +

    Other than using async with, you can also manually control the +transaction:

    +
    tx = await connection.transaction()
    +try:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    You can't use raise_commit() or +raise_rollback() here, similarly it is +prohibited to use commit() and +rollback() in an async with block.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/index.html b/docs/zh/1.0/index.html new file mode 100644 index 0000000..63fcc55 --- /dev/null +++ b/docs/zh/1.0/index.html @@ -0,0 +1,226 @@ + + + + + + + + 欢迎来到 GINO 的文档! - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    欢迎来到 GINO 的文档!

    +PyPI Release Version +GitHub Workflow Status for tests +Codacy coverage +Dependabot +

    GINO 递归定义为 GINO Is Not ORM,是一个基于 asyncioSQLAlchemy core 的轻量级异步 Python ORM 框架,目前(2020 年初)仅支持 asyncpg 一种引擎。

    + + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/objects.inv b/docs/zh/1.0/objects.inv new file mode 100644 index 0000000..ff9c15d Binary files /dev/null and b/docs/zh/1.0/objects.inv differ diff --git a/docs/zh/1.0/py-modindex.html b/docs/zh/1.0/py-modindex.html new file mode 100644 index 0000000..18dcbf9 --- /dev/null +++ b/docs/zh/1.0/py-modindex.html @@ -0,0 +1 @@ +

    domainindex.html

    \ No newline at end of file diff --git a/docs/zh/1.0/reference.html b/docs/zh/1.0/reference.html new file mode 100644 index 0000000..9735c28 --- /dev/null +++ b/docs/zh/1.0/reference.html @@ -0,0 +1,327 @@ + + + + + + + + 参考手册 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    参考手册

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api.html b/docs/zh/1.0/reference/api.html new file mode 100644 index 0000000..9e170b8 --- /dev/null +++ b/docs/zh/1.0/reference/api.html @@ -0,0 +1,237 @@ + + + + + + + + API 参考 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.aiocontextvars.html b/docs/zh/1.0/reference/api/gino.aiocontextvars.html new file mode 100644 index 0000000..c9181c7 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.aiocontextvars.html @@ -0,0 +1,213 @@ + + + + + + + + gino.aiocontextvars module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.aiocontextvars module

    +
    +
    +gino.aiocontextvars.patch_asyncio()
    +

    Patches asyncio to support contextvars.

    +

    This is automatically called when gino is imported. If Python version is 3.7 +or greater, this function is a no-op.

    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.api.html b/docs/zh/1.0/reference/api/gino.api.html new file mode 100644 index 0000000..43ffabf --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.api.html @@ -0,0 +1,584 @@ + + + + + + + + gino.api module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.api module

    +
    +
    +class gino.api.Gino(bind=None, model_classes=None, query_ext=True, schema_ext=True, ext=True, **kwargs)
    +

    基类:sqlalchemy.sql.schema.MetaData

    +

    All-in-one API class of GINO, providing several shortcuts.

    +

    This class is a subclass of SQLAlchemy +MetaData, therefore its instances can be used +as a normal MetaData object, e.g. used in +Alembic. In usual cases, you would +want to define one global Gino instance, usually under the name +of db, representing the database used in your application.

    +

    You may define tables in the official way +SQLAlchemy core recommended, but more often in GINO we define model classes +with db.Model as their parent class to represent tables, for its +objective interface and CRUD operations. Please read 增删改查 +for more information.

    +

    For convenience, Gino instance delegated all properties publicly +exposed by sqlalchemy, so that you can define tables / models +without importing sqlalchemy:

    +
    id = db.Column(db.BigInteger(), primary_key=True)
    +
    +
    +

    Similar to MetaData, a Gino object +can bind to a GinoEngine instance, hereby allowing +"implicit execution" through the gino +extension on Executable or +SchemaItem constructs:

    +
    await User.query.gino.first()
    +await db.gino.create_all()
    +
    +
    +

    Differently, GINO encourages the use of implicit execution and manages +transactional context correctly.

    +

    Binding to a connection object is not supported.

    +

    To set a bind property, you can simply set your +GinoEngine object on db.bind, or +set it to None to unbind. However, the creation of engine usually +happens at the same time. Therefore, GINO provided several convenient ways +doing so:

    +
      +
    1. with_bind() returning an asynchronous context manager:

      +
      async with db.with_bind('postgresql://...') as engine:
      +
      +
      +
    2. +
    3. set_bind() and pop_bind():

      +
      engine = await db.set_bind('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    4. +
    5. Directly await on Gino instance:

      +
      db = await gino.Gino('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    6. +
    +
    +

    注解

    +

    SQLAlchemy allows creating the engine by:

    +
    metadata.bind = 'postgresql://...'
    +
    +
    +

    While in GINO this only sets a string to bind, because +creating an engine requires await, which is exactly what +set_bind() does.

    +
    +

    At last, Gino delegates all query APIs on the bound +GinoEngine.

    +
    +
    +property Model
    +

    Declarative base class for models, subclass of +gino.declarative.Model. Defining subclasses of this class will +result new tables added to this Gino metadata.

    +
    + +
    +
    +acquire(*args, **kwargs)
    +

    A delegate of GinoEngine.acquire().

    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.all().

    +
    + +
    +
    +property bind
    +

    An GinoEngine to which this Gino is bound.

    +

    This is a simple property with no getter or setter hook - what you set +is what you get. To achieve the same result as it is in SQLAlchemy - +setting a string or URL and getting an +engine instance, use set_bind() (or await on this +Gino object after setting a string or +URL).

    +
    + +
    +
    +compile(elem, *multiparams, **params)
    +

    A delegate of GinoEngine.compile().

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.first().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.iterate().

    +
    + +
    +
    +model_base_classes = (<class 'gino.crud.CRUDModel'>,)
    +

    Overridable default model classes to build the Model.

    +

    Default is (CRUDModel, ).

    +
    + +
    +
    +no_delegate = {'create_engine', 'engine_from_config'}
    +

    A set of symbols from sqlalchemy which is not delegated by +Gino.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one_or_none().

    +
    + +
    +
    +pop_bind()
    +

    Unbind self, and return the bound engine.

    +

    This is usually used in a chained call to close the engine:

    +
    await db.pop_bind().close()
    +
    +
    +
    +
    返回
    +

    GinoEngine or None if self is not bound.

    +
    +
    +
    + +
    +
    +query_executor
    +

    The overridable gino extension class on +Executable.

    +

    This class will be set as the getter method of the property gino on +Executable and its subclasses, if +ext and query_ext arguments are both True. Default is +GinoExecutor.

    +

    GinoExecutor 的别名

    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.scalar().

    +
    + +
    +
    +schema_visitor
    +

    gino.schema.GinoSchemaVisitor 的别名

    +
    + +
    +
    +async set_bind(bind, loop=None, **kwargs)
    +

    Bind self to the given GinoEngine and return it.

    +

    If the given bind is a string or +URL, all arguments will be sent to +create_engine() to create a new engine, and return it.

    +
    +
    返回
    +

    GinoEngine

    +
    +
    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.status().

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    A delegate of GinoEngine.transaction().

    +
    + +
    +
    +with_bind(bind, loop=None, **kwargs)
    +

    Shortcut for set_bind() and pop_bind() plus closing engine.

    +

    This method accepts the same arguments of +create_engine(). This allows inline creating an engine and +binding self on enter, and unbinding self and closing the engine on +exit:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # play with engine
    +
    +
    +
    +
    返回
    +

    An asynchronous context manager.

    +
    +
    +
    + +
    + +
    +
    +class gino.api.GinoExecutor(query)
    +

    基类:object

    +

    The default gino extension on +Executable constructs for implicit +execution.

    +

    Instances of this class are created when visiting the gino property of +Executable instances (also referred as +queries or clause elements), for example:

    +
    await User.query.gino.first()
    +
    +
    +

    This allows GINO to add the asynchronous query APIs (all(), +first(), one(), one_or_none(), scalar(), +status(), iterate()) to SQLAlchemy query clauses without +messing up with existing synchronous ones. +Calling these asynchronous query APIs has the same restriction - the +relevant metadata (the Gino instance) must be bound to an engine, +or an AttributeError will be raised.

    +
    +

    注解

    +

    Executable clause elements that are completely irrelevant with any +table - for example db.select([db.text('now()')]) - has no +metadata, hence no engine. Therefore, this will always fail:

    +
    await db.select([db.text('now()')]).gino.scalar()
    +
    +
    +

    You should use conn.scalar(), +engine.scalar() or even +db.scalar() in this case.

    +
    +
    +
    +async all(*multiparams, **params)
    +

    Returns engine.all() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async first(*multiparams, **params)
    +

    Returns engine.first() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +iterate(*multiparams, **params)
    +

    Returns engine.iterate() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +load(value)
    +

    Shortcut to set execution option loader in a chaining call.

    +

    For example to load Book instances with their authors:

    +
    query = Book.join(User).select()
    +books = await query.gino.load(Book.load(author=User)).all()
    +
    +
    +

    Read execution_options() for more +information.

    +
    + +
    +
    +model(model)
    +

    Shortcut to set execution option model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async one(*multiparams, **params)
    +

    Returns engine.one() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async one_or_none(*multiparams, **params)
    +

    Returns engine.one_or_none() +with this query as the first argument, and other arguments followed, +where engine is the GinoEngine to which the +metadata (Gino) is bound, while metadata is found in this +query.

    +
    + +
    +
    +property query
    +

    Get back the chained Executable.

    +

    In a chained query calls, occasionally the previous query clause is +needed after a .gino. chain, you can use .query. to resume the +chain back. For example:

    +
    await User.query.gino.model(FOUser).query.where(...).gino.all()
    +
    +
    +
    + +
    +
    +return_model(switch)
    +

    Shortcut to set execution option return_model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async scalar(*multiparams, **params)
    +

    Returns engine.scalar() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async status(*multiparams, **params)
    +

    Returns engine.status() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +timeout(timeout)
    +

    Shortcut to set execution option timeout in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.crud.html b/docs/zh/1.0/reference/api/gino.crud.html new file mode 100644 index 0000000..0e54655 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.crud.html @@ -0,0 +1,650 @@ + + + + + + + + gino.crud module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.crud module

    +
    +
    +class gino.crud.Alias(model, *args, **kwargs)
    +

    基类:object

    +

    Experimental proxy for table alias on model.

    +
    +
    +distinct(*columns)
    +
    + +
    +
    +load(*column_names, **relationships)
    +
    + +
    +
    +on(on_clause)
    +
    + +
    + +
    +
    +class gino.crud.CRUDModel(**values)
    +

    基类:gino.declarative.Model

    +

    The base class for models with CRUD support.

    +

    Don't inherit from this class directly, because it has no metadata. Use +db.Model instead.

    +
    +
    +classmethod alias(*args, **kwargs)
    +

    Experimental proxy for table alias on model.

    +
    + +
    +
    +append_where_primary_key(q)
    +

    Append where clause to locate this model instance by primary on the +given query, and return the new query.

    +

    This is mostly used internally in GINO, but also available for such +usage:

    +
    await user.append_where_primary_key(User.query).gino.first()
    +
    +
    +

    which is identical to:

    +
    await user.query.gino.first()
    +
    +
    +
    +

    0.7.6 版后已移除: Use lookup() instead.

    +
    +
    + +
    +
    +create(bind=None, timeout=<object object>, **values)
    +

    This create behaves a bit different on model classes compared to model +instances.

    +

    On model classes, create will create a new model instance and insert +it into database. On model instances, create will just insert the +instance into the database.

    +

    Under the hood create() uses INSERT ... RETURNING ... to +create the new model instance and load it with database default data if +not specified.

    +

    Some examples:

    +
    user1 = await User.create(name='fantix', age=32)
    +user2 = User(name='gino', age=42)
    +await user2.create()
    +
    +
    +
    +
    参数
    +
      +
    • bind -- A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    • values -- Keyword arguments are pairs of attribute names and their +initial values. Only available when called on a model class.

    • +
    +
    +
    返回
    +

    The instance of this model class (newly created or existing).

    +
    +
    +
    + +
    +
    +delete
    +

    Similar to update(), this delete is also different on model +classes than on model instances.

    +

    On model classes delete is an attribute of type +Delete for massive deletes, for +example:

    +
    await User.delete.where(User.enabled.is_(False)).gino.status()
    +
    +
    +

    Similarly you can add a returning() +clause to the query and it shall return the deleted rows as model objects.

    +

    And on model instances, delete() is a method to remove the +corresponding row in the database of this model instance. and returns the +status returned from the database:

    +
    print(await user.delete())  # e.g. prints DELETE 1
    +
    +
    +
    +

    注解

    +

    delete() only removes the row from database, it does not affect the +current model instance.

    +
    +
    +
    参数
    +
      +
    • bind -- An optional GinoEngine if current +metadata (Gino) has no bound engine, or specifying a +different GinoEngine to execute the DELETE.

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    +
    + +
    +
    +classmethod distinct(*columns)
    +

    Experimental loader feature to yield only distinct instances by given +columns.

    +
    + +
    +
    +async classmethod get(ident, bind=None, timeout=<object object>)
    +

    Get an instance of this model class by primary key.

    +

    For example:

    +
    user = await User.get(request.args.get('user_id'))
    +
    +
    +
    +
    参数
    +
      +
    • ident -- Value of the primary key. For composite primary keys this +should be a tuple of values for all keys in database order, or a dict +of names (or position numbers in database order starting from zero) +of all primary keys to their values.

    • +
    • bind -- A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    返回
    +

    An instance of this model class, or None if no such row.

    +
    +
    +
    + +
    +
    +classmethod in_query(query)
    +

    Convenient method to get a Model object when using subqueries.

    +

    Though with filters and aggregations, subqueries often return same +columns as the original table, but SQLAlchemy could not recognize them +as the columns are in subqueries, so technically they're columns in the +new "table".

    +

    With this method, the columns are loaded into the original models when +being used in subqueries. For example:

    +
    query = query.alias('users')
    +MyUser = User.in_query(query)
    +
    +loader = MyUser.distinct(User1.id).load()
    +users = await query.gino.load(loader).all()
    +
    +
    +
    + +
    +
    +classmethod load(*column_names, **relationships)
    +

    Populates a loader.Loader instance to be used by the +loader execution option in order to customize the loading behavior +to load specified fields into instances of this model.

    +

    The basic usage of this method is to provide the loader execution +option (if you are looking for reloading +the instance from database, check get() or query) for a +given query.

    +

    This method takes both positional arguments and keyword arguments with +very different meanings. The positional arguments should be column +names as strings, specifying only these columns should be loaded into +the model instance (other values are discarded even if they are +retrieved from database). Meanwhile, the keyword arguments should be +loaders for instance attributes. For example:

    +
    u = await User.query.gino.load(User.load('id', 'name')).first()
    +
    +
    +
    +

    小技巧

    +

    gino.load is a shortcut for setting the execution option +loader.

    +
    +

    This will populate a User instance with only id and name +values, all the rest are simply None even if the query actually +returned all the column values.

    +
    q = User.join(Team).select()
    +u = await q.gino.load(User.load(team=Team)).first()
    +
    +
    +

    This will load two instances of model User and Team, returning +the User instance with u.team set to the Team instance.

    +

    Both positional and keyword arguments can be used ath the same time. If +they are both omitted, like Team.load(), it is equivalent to just +Team as a loader.

    +

    Additionally, a loader.Loader instance can also be used to +generate queries, as its structure is usually the same as the query:

    +
    u = await User.load(team=Team).query.gino.first()
    +
    +
    +

    This generates a query like this:

    +
    SELECT users.xxx, ..., teams.xxx, ...
    +  FROM users LEFT JOIN teams
    +    ON ...
    +
    +
    +

    The Loader delegates attributes on the query, so +.query can be omitted. The LEFT JOIN is built-in behavior, +while the ON clause is generated based on foreign key. If there is +no foreign key, or the condition should be customized, you can use +this:

    +
    u = await User.load(
    +    team=Team.on(User.team_id == Team.id)).gino.first()
    +
    +
    +

    And you can use both load() and on() at the same time +in a chain, in whatever order suits you.

    + +
    + +
    +
    +lookup()
    +

    Generate where-clause expression to locate this model instance.

    +

    By default this method uses current values of all primary keys, and you +can override it to behave differently. Most instance-level CRUD +operations depend on this method internally. Particularly while +lookup() is called in update(), the where condition is +used in UpdateRequest.apply(), so that queries like UPDATE ... +SET id = NEW WHERE id = OLD could work correctly.

    +
    +
    返回
    +

    +
    +
    +
    +

    0.7.6 新版功能.

    +
    +
    + +
    +
    +classmethod none_as_none(enabled=True)
    +
    + +
    +
    +classmethod on(on_clause)
    +

    Customize the on-clause for the auto-generated outer join query.

    +
    +

    注解

    +

    This has no effect when provided as the loader execution option +for a given query.

    +
    +
    +

    参见

    +

    load()

    +
    +
    + +
    +
    +query
    +

    Get a SQLAlchemy query clause of the table behind this model. This equals +to sqlalchemy.select([self.__table__]). If this attribute is retrieved on a +model instance, then a where clause to locate this instance by its primary +key is appended to the returning query clause. This model type is set as +the execution option model in the returning clause, so by default the +query yields instances of this model instead of database rows.

    +
    + +
    +
    +select()
    +

    Build a query to retrieve only specified columns from this table.

    +

    This method accepts positional string arguments as names of attributes to +retrieve, and returns a Select for +query. The returning query object is always set with two execution options:

    +
      +
    1. model is set to this model type

    2. +
    3. return_model is set to False

    4. +
    +

    So that by default it always return rows instead of model instances, while +column types can be inferred correctly by the model option.

    +

    For example:

    +
    async for row in User.select('id', 'name').gino.iterate():
    +    print(row['id'], row['name'])
    +
    +
    +

    If select() is invoked on a model instance, then a WHERE clause +to locate this instance by its primary key is appended to the returning +query clause. This is useful when you want to retrieve a latest value of a +field on current model instance from database:

    +
    db_age = await user.select('age').gino.scalar()
    +
    +
    + +
    + +
    +
    +to_dict()
    +

    Convenient method to generate a dict from this model instance.

    +

    Keys will be attribute names, while values are loaded from memory (not +from database). If there are JSONProperty +attributes in this model, their source JSON field will not be included +in the returning dict - instead the JSON attributes will be.

    +
    +

    参见

    +

    json_support

    +
    +
    + +
    +
    +update
    +

    This update behaves quite different on model classes rather than model +instances.

    +

    On model classes, update is an attribute of type +Update for massive updates, for +example:

    +
    await User.update.values(enabled=True).where(...).gino.status()
    +
    +
    +

    Like query, the update query also has the model execution +option of this model, so if you use the +returning() clause, the query shall +return model objects.

    +

    However on model instances, update() is a method which accepts keyword +arguments only and returns an UpdateRequest to update this single +model instance. The keyword arguments are pairs of attribute names and new +values. This is the same as UpdateRequest.update(), feel free to +read more about it. A normal usage example would be like this:

    +
    await user.update(name='new name', age=32).apply()
    +
    +
    +

    Here, the await ... apply() executes the +actual UPDATE SQL in the database, while user.update() only makes +changes in the memory, and collect all changes into an +UpdateRequest instance.

    +
    + +
    + +
    +
    +class gino.crud.QueryModel
    +

    基类:type

    +

    Metaclass of Model classes used for subqueries.

    +
    + +
    +
    +class gino.crud.UpdateRequest(instance: gino.crud.CRUDModel)
    +

    基类:object

    +

    A collection of attributes and their new values to update on one model +instance.

    +

    UpdateRequest instances are created by CRUDModel.update, +don't instantiate manually unless required. Every UpdateRequest +instance is bound to one model instance, all updates are for that one +specific model instance and its database row.

    +
    +
    +async apply(bind=None, timeout=<object object>)
    +

    Apply pending updates into database by executing an UPDATE SQL.

    +
    +
    参数
    +
      +
    • bind -- A GinoEngine to execute the SQL, or +None (default) to use the bound engine in the metadata.

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    返回
    +

    self for chaining calls.

    +
    +
    +
    + +
    +
    +update(**values)
    +

    Set given attributes on the bound model instance, and add them into +the update collections for apply().

    +

    Given keyword-only arguments are pairs of attribute names and values to +update. This is not a coroutine, calling update() will have +instant effect on the bound model instance - its in-memory values will +be updated immediately. Therefore this can be used individually as a +shortcut to update several attributes in a batch:

    +
    user.update(age=32, disabled=True)
    +
    +
    +

    update() returns self for chaining calls to either +apply() or another update(). If one attribute is updated +several times by the same UpdateRequest, then only the last +value is remembered for apply().

    +

    Updated values can be SQLAlchemy expressions, for example an atomic +increment for user balance looks like this:

    +
    await user.update(balance=User.balance + 100).apply()
    +
    +
    +
    +

    注解

    +

    Expression values will not affect the in-memory attribute value on +update() before apply(), because it has no knowledge +of the latest value in the database. After apply() the new +value will be automatically reloaded from database with +RETURNING clause.

    +
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.declarative.html b/docs/zh/1.0/reference/api/gino.declarative.html new file mode 100644 index 0000000..07c6d5b --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.declarative.html @@ -0,0 +1,382 @@ + + + + + + + + gino.declarative module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.declarative module

    +
    +
    +class gino.declarative.ColumnAttribute(prop_name, column)
    +

    基类:object

    +

    The type of the column wrapper attributes on GINO models.

    +

    This is the core utility to enable GINO models so that:

    +
      +
    • Accessing a column attribute on a model class returns the column itself

    • +
    • Accessing a column attribute on a model instance returns the value for that column

    • +
    +

    This utility is customizable by defining __attr_factory__ in the model class.

    +
    + +
    +
    +class gino.declarative.InvertDict(*args, **kwargs)
    +

    基类:dict

    +

    A custom dict that allows getting keys by values.

    +

    Used internally by Model.

    +
    +
    +invert_get(value, default=None)
    +

    Get key by value.

    +
    +
    参数
    +
      +
    • value -- A value in this dict.

    • +
    • default -- If specified value doesn't exist, return default.

    • +
    +
    +
    返回
    +

    The corresponding key if the value is found, or default otherwise.

    +
    +
    +
    + +
    + +
    +
    +class gino.declarative.Model
    +

    基类:object

    +

    The base class of GINO models.

    +

    This is not supposed to be sub-classed directly, declarative_base() should +be used instead to generate a base model class with a given +MetaData. By defining subclasses of a model, instances +of sqlalchemy.schema.Table will be created and added to the bound +MetaData. The columns of the +Table instance are defined as +Column attributes:

    +
    from sqlalchemy import MetaData, Column, Integer, String
    +from gino.declarative import declarative_base
    +
    +Model = declarative_base()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = Column(Integer(), primary_key=True)
    +    name = Column(String())
    +
    +
    +

    The name of the columns are automatically set using the attribute name.

    +

    An instance of a model will maintain a memory storage for values of all the defined +column attributes. You can access these values by the same attribute name, or update +with new values, just like normal Python objects:

    +
    u = User()
    +assert u.name is None
    +
    +u.name = "daisy"
    +assert u.name == "daisy"
    +
    +
    +
    +

    注解

    +

    Accessing column attributes on a model instance will NOT trigger any database +operation.

    +
    +

    Constraint and Index are +also allowed as model class attributes. Their attribute names are not used.

    +

    A concrete model class can be used as a replacement of the +Table it reflects in SQLAlchemy queries. The model class +is also iterable, yielding all the Column instances +defined in the model.

    +

    Other built-in class attributes:

    +
      +
    • __metadata__

      +

      This is supposed to be set by declarative_base() and used only during +subclass construction. Still, this can be treated as a read-only attribute to +find out which MetaData this model is bound to.

      +
    • +
    • __tablename__

      +

      This is a required attribute to define a concrete model, meaning a +sqlalchemy.schema.Table instance will be created, added to the bound +MetaData and set to the class attribute __table__. +Not defining __tablename__ will result in an abstract model - no table +instance will be created, and instances of an abstract model are meaningless.

      +
    • +
    • __table__

      +

      This should usually be treated as an auto-generated read-only attribute storing +the sqlalchemy.schema.Table instance.

      +
    • +
    • __attr_factory__

      +

      An attribute factory that is used to wrap the actual +Column instance on the model class, so that the access +to the column attributes on model instances is redirected to the in-memory value +store. The default factory is ColumnAttribute, can be override.

      +
    • +
    • __values__

      +

      The internal in-memory value store as a dict, only available on model +instances. Accessing column attributes is equivalent to accessing __values__.

      +
    • +
    +
    + +
    +
    +gino.declarative.declarative_base(metadata, model_classes=(<class 'gino.declarative.Model'>, ), name='Model')
    +

    Create a base GINO model class for declarative table definition.

    +
    +
    参数
    +
      +
    • metadata -- A MetaData instance to contain the +tables.

    • +
    • model_classes -- Base class(es) of the base model class to be created. Default: +Model.

    • +
    • name -- The class name of the base model class to be created. Default: +Model.

    • +
    +
    +
    返回
    +

    A new base model class.

    +
    +
    +
    + +
    +
    +gino.declarative.declared_attr(m)
    +

    Mark a class-level method as a factory of attribute.

    +

    This is intended to be used as decorators on class-level methods of a +Model class. When initializing the class as well as its +subclasses, the decorated factory method will be called for each class, the +returned result will be set on the class in place of the factory method +under the same name.

    +

    @declared_attr is implemented differently than +declared_attr of SQLAlchemy, but they +are both more often used on mixins to dynamically declare indices or +constraints (also works for column and __table_args__, or even normal +class attributes):

    +
    class TrackedMixin:
    +    created = db.Column(db.DateTime(timezone=True))
    +
    +    @db.declared_attr
    +    def unique_id(cls):
    +        return db.Column(db.Integer())
    +
    +    @db.declared_attr
    +    def unique_constraint(cls):
    +        return db.UniqueConstraint('unique_id')
    +
    +    @db.declared_attr
    +    def poly(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.Column(db.Unicode())
    +
    +    @db.declared_attr
    +    def __table_args__(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.UniqueConstraint('poly'),
    +
    +
    +
    +

    注解

    +

    This doesn't work if the model already had a __table__.

    +
    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.dialects.asyncpg.html b/docs/zh/1.0/reference/api/gino.dialects.asyncpg.html new file mode 100644 index 0000000..23a5f6e --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.dialects.asyncpg.html @@ -0,0 +1,590 @@ + + + + + + + + gino.dialects.asyncpg module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.asyncpg module

    +
    +
    +class gino.dialects.asyncpg.AsyncEnum(*enums, **kw)
    +

    基类:sqlalchemy.dialects.postgresql.base.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCompiler(dialect, statement, column_keys=None, inline=False, **kwargs)
    +

    基类:sqlalchemy.dialects.postgresql.base.PGCompiler

    +
    +
    +property bindtemplate
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCursor(context, cursor)
    +

    基类:gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDBAPI
    +

    基类:gino.dialects.base.BaseDBAPI

    +
    +
    +Error = (<class 'asyncpg.exceptions._base.PostgresError'>, <class 'asyncpg.exceptions._base.InterfaceError'>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDialect(*args, **kwargs)
    +

    基类:sqlalchemy.dialects.postgresql.base.PGDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.sql.sqltypes.ARRAY'>: <class 'sqlalchemy.dialects.postgresql.array.ARRAY'>, <class 'sqlalchemy.sql.sqltypes.Interval'>: <class 'sqlalchemy.dialects.postgresql.base.INTERVAL'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'gino.dialects.asyncpg.AsyncpgJSONPathType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.postgresql.json.JSON'>, <class 'sqlalchemy.dialects.postgresql.base.ENUM'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.asyncpg.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    DBAPICursor 的别名

    +
    + +
    +
    +dbapi_class
    +

    AsyncpgDBAPI 的别名

    +
    + +
    +
    +driver = 'asyncpg'
    +
    + +
    +
    +execution_ctx_cls
    +

    AsyncpgExecutionContext 的别名

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given an asyncpg connection, return its isolation level.

    +
    + +
    +
    +async has_schema(connection, schema)
    +
    + +
    +
    +async has_sequence(connection, sequence_name, schema=None)
    +

    Check the existence of a particular sequence in the database.

    +

    Given a _engine.Connection object and a string +sequence_name, return True if the given sequence exists in +the database, False otherwise.

    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +async has_type(connection, type_name, schema=None)
    +
    + +
    +
    +init_kwargs = {'command_timeout', 'connection_class', 'database', 'host', 'init', 'loop', 'max_cacheable_statement_size', 'max_cached_statement_lifetime', 'max_inactive_connection_lifetime', 'max_queries', 'max_size', 'min_size', 'passfile', 'password', 'port', 'server_settings', 'setup', 'ssl', 'statement_cache_size', 'timeout', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument "conn" which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The "do_on_connect" callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    返回
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    参见

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given an asyncpg connection, set its isolation level.

    +
    + +
    +
    +statement_compiler
    +

    AsyncpgCompiler 的别名

    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgExecutionContext
    +

    基类:gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.postgresql.base.PGExecutionContext

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgIterator(context, iterator)
    +

    基类:object

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgJSONPathType
    +

    基类:sqlalchemy.dialects.postgresql.json.JSONPathType

    +
    +
    +bind_processor(dialect)
    +

    Return a conversion function for processing bind values.

    +

    Returns a callable which will receive a bind parameter value +as the sole positional argument and will return a value to +send to the DB-API.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +

    dialect -- Dialect instance in use.

    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.DBAPICursor(dbapi_conn)
    +

    基类:gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.GinoNullType
    +

    基类:sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +
      +
    • dialect -- Dialect instance in use.

    • +
    • coltype -- DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.NullPool(url, loop, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.Pool(url, loop, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.PreparedStatement(prepared, clause=None)
    +

    基类:gino.dialects.base.PreparedStatement

    +
    + +
    +
    +class gino.dialects.asyncpg.Transaction(tx)
    +

    基类:gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.dialects.base.html b/docs/zh/1.0/reference/api/gino.dialects.base.html new file mode 100644 index 0000000..d7cbb76 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.dialects.base.html @@ -0,0 +1,449 @@ + + + + + + + + gino.dialects.base module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.base module

    +
    +
    +class gino.dialects.base.AsyncDialectMixin
    +

    基类:object

    +
    +
    +compile(elem, *multiparams, **params)
    +
    + +
    +
    +cursor_cls
    +

    DBAPICursor 的别名

    +
    + +
    +
    +classmethod dbapi()
    +
    + +
    +
    +dbapi_class
    +

    BaseDBAPI 的别名

    +
    + +
    +
    +async init_pool(url, loop)
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.base.BaseDBAPI
    +

    基类:object

    +
    +
    +static Binary(x)
    +
    + +
    +
    +Error
    +

    builtins.Exception 的别名

    +
    + +
    +
    +paramstyle = 'numeric'
    +
    + +
    + +
    +
    +class gino.dialects.base.Cursor
    +

    基类:object

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.base.DBAPICursor
    +

    基类:object

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +execute(statement, parameters)
    +
    + +
    +
    +executemany(statement, parameters)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.base.ExecutionContextOverride
    +

    基类:object

    +
    +
    +get_result_proxy()
    +
    + +
    +
    +loader
    +
    + +
    +
    +model
    +
    + +
    +
    +process_rows(rows, return_model=True)
    +
    + +
    +
    +return_model
    +
    + +
    +
    +timeout
    +
    + +
    + +
    +
    +class gino.dialects.base.Pool
    +

    基类:object

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.base.PreparedStatement(clause=None)
    +

    基类:object

    +
    +
    +async all(*multiparams, **params)
    +
    + +
    +
    +async first(*multiparams, **params)
    +
    + +
    +
    +iterate(*params, **kwargs)
    +
    + +
    +
    +async scalar(*multiparams, **params)
    +
    + +
    +
    +async status(*multiparams, **params)
    +
    + +
    + +
    +
    +class gino.dialects.base.Transaction
    +

    基类:object

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.dialects.html b/docs/zh/1.0/reference/api/gino.dialects.html new file mode 100644 index 0000000..400d674 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.dialects.html @@ -0,0 +1,223 @@ + + + + + + + + gino.dialects package - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects package

    + +
    +

    Module contents

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.engine.html b/docs/zh/1.0/reference/api/gino.engine.html new file mode 100644 index 0000000..3762854 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.engine.html @@ -0,0 +1,715 @@ + + + + + + + + gino.engine module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.engine module

    +
    +
    +class gino.engine.GinoConnection(dialect, sa_conn, stack=None)
    +

    基类:object

    +

    Represents an actual database connection.

    +

    This is the root of all query API like all(), first(), +one(), one_or_none(), scalar() or status(), +those on engine or query are simply wrappers of methods in this class.

    +

    Usually instances of this class are created by GinoEngine.acquire().

    +
    +

    注解

    +

    GinoConnection may refer to zero or one underlying database +connection - when a GinoConnection is acquired with +lazy=True, the underlying connection may still be in the pool, +until a query API is called or get_raw_connection() is called.

    +

    Oppositely, one underlying database connection can be shared by many +GinoConnection instances when they are acquired with +reuse=True. The actual database connection is only returned to the +pool when the root GinoConnection is released. Read more +in GinoEngine.acquire() method.

    +
    +
    +

    参见

    +

    引擎与连接

    +
    +
    +
    +async all(clause, *multiparams, **params)
    +

    Runs the given query in database, returns all results as a list.

    +

    This method accepts the same parameters taken by SQLAlchemy +execute(). You can pass in a raw +SQL string, or any SQLAlchemy query clauses.

    +

    If the given query clause is built by CRUD models, then the returning +rows will be turned into relevant model objects (Only one type of model +per query is supported for now, no relationship support yet). See +execution_options() for more information.

    +

    If the given parameters are parsed as "executemany" - bulk inserting +multiple rows in one call for example, the returning result from +database will be discarded and this method will return None.

    +
    + +
    +
    +property dialect
    +

    The Dialect in use, inherited +from the engine created this connection.

    +
    + +
    +
    +execution_options(**opt)
    +

    Set non-SQL options for the connection which take effect during +execution.

    +

    This method returns a copy of this GinoConnection which +references the same underlying database connection, but with the given +execution options set on the copy. Therefore, it is a good practice to +discard the copy immediately after use, for example:

    +
    row = await conn.execution_options(model=None).first(User.query)
    +
    +
    +

    This is very much the same as SQLAlchemy +execution_options(), it +actually does pass the execution options to the underlying SQLAlchemy +Connection. Furthermore, GINO added a +few execution options:

    +
    +
    参数
    +
      +
    • return_model -- Boolean to control whether the returning results +should be loaded into model instances, where the model class is +defined in another execution option model. Default is True.

    • +
    • model -- Specifies the type of model instance to create on return. +This has no effect if return_model is set to False. Usually +in queries built by CRUD models, this execution option is +automatically set. For now, GINO only supports loading each row into +one type of model object, relationships are not supported. Please use +multiple queries for that. None for no postprocessing (default).

    • +
    • timeout -- Seconds to wait for the query to finish. None for +no time out (default).

    • +
    • loader --

      A loader expression to load the database rows into +specified objective structure. It can be either:

      +
        +
      • A model class, so that the query will yield model instances of this +class. It is your responsibility to make sure all the columns of +this model is selected in the query.

      • +
      • A Column instance, so that each result +will be only a single value of this column. Please note, if you +want to achieve fetching the very first value, you should use +first() instead of +scalar(). However, using directly +scalar() is a more direct way.

      • +
      • A tuple nesting more loader expressions recursively.

      • +
      • A callable() function that will be called for each row to +fully customize the result. Two positional arguments will be passed +to the function: the first is the row instance, the second is a context +object which is only present if nested else None.

      • +
      • A Loader instance directly.

      • +
      • Anything else will be treated as literal values thus returned as +whatever they are.

      • +
      +

    • +
    +
    +
    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +async get_raw_connection(*, timeout=None)
    +

    Get the underlying database connection, acquire one if none present.

    +
    +
    参数
    +

    timeout -- Seconds to wait for the underlying acquiring

    +
    +
    返回
    +

    Underlying database connection instance depending on the +dialect in use

    +
    +
    Raises
    +

    TimeoutError if the acquiring timed out

    +
    +
    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    Cursors must work within transactions:

    +
    async with conn.transaction():
    +    async for user in conn.iterate(User.query):
    +        # handle each user without loading all users into memory
    +
    +
    +

    Alternatively, you can manually control how the cursor works:

    +
    async with conn.transaction():
    +    cursor = await conn.iterate(User.query)
    +    user = await cursor.next()
    +    users = await cursor.many(10)
    +
    +
    +

    Read more about how Cursor works.

    +

    Similarly, this method takes the same parameters as all().

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs the given query in database, returns exactly one result.

    +

    If the query returns no result, this method will raise +NoResultFound. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs the given query in database, returns at most one result.

    +

    If the query returns no result, this method will return None. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async prepare(clause)
    +
    + +
    +
    +property raw_connection
    +

    The current underlying database connection instance, type depends on +the dialect in use. May be None if self is a lazy connection.

    +
    + +
    +
    +async release(*, permanent=True)
    +

    Returns the underlying database connection to its pool.

    +

    If permanent=False, this connection will be set in lazy mode with +underlying database connection returned, the next query on this +connection will cause a new database connection acquired. This is +useful when this connection may still be useful again later, while some +long-running I/O operations are about to take place, which should not +take up one database connection or even transaction for that long time.

    +

    Otherwise with permanent=True (default), this connection will be +marked as closed after returning to pool, and be no longer usable +again.

    +

    If this connection is a reusing connection, then only this connection +is closed (depending on permanent), the reused underlying +connection will not be returned back to the pool.

    +

    Practically it is recommended to return connections in the reversed +order as they are borrowed, but if this connection is a reused +connection with still other opening connections reusing it, then on +release the underlying connection will be returned to the pool, +with all the reusing connections losing an available underlying +connection. The availability of further operations on those reusing +connections depends on the given permanent value.

    + +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +schema_for_object = <sqlalchemy.sql.schema._SchemaTranslateMap object>
    +

    A SQLAlchemy compatibility attribute, don't use it for now, it bites.

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the query status.

    +

    The returning query status depends on underlying database and the +dialect in use. For asyncpg it is a string, you can parse it like this: +https://git.io/v7oze

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    Starts a database transaction.

    +

    There are two ways using this method: managed as an asynchronous +context manager:

    +
    async with conn.transaction() as tx:
    +    # run query in transaction
    +
    +
    +

    or manually awaited:

    +
    tx = await conn.transaction()
    +try:
    +    # run query in transaction
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    Where the tx is an instance of the +GinoTransaction class, feel free to read +more about it.

    +

    In the first managed mode, the transaction is automatically committed +on exiting the context block, or rolled back if an exception was raised +which led to the exit of the context. In the second manual mode, you'll +need to manually call the +commit() or +rollback() methods on need.

    +

    If this is a lazy connection, entering a transaction will cause a new +database connection acquired if none was present.

    +

    Transactions may support nesting depending on the dialect in use. For +example in asyncpg, starting a second transaction on the same +connection will create a save point in the database.

    +

    For now, the parameters are directly passed to underlying database +driver, read asyncpg.connection.Connection.transaction() for +asyncpg.

    +
    + +
    + +
    +
    +class gino.engine.GinoEngine(dialect, pool, loop, logging_name=None, echo=None, execution_options=None)
    +

    基类:object

    +

    Connects a Pool and +Dialect together to provide a source +of database connectivity and behavior.

    +

    A GinoEngine object is instantiated publicly using the +gino.create_engine() function or +db.set_bind() method.

    +
    +

    参见

    +

    引擎与连接

    +
    +
    +
    +acquire(*, timeout=None, reuse=False, lazy=False, reusable=True)
    +

    Acquire a connection from the pool.

    +

    There are two ways using this method - as an asynchronous context +manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    which will guarantee the connection is returned to the pool when +leaving the async with block; or as a coroutine:

    +
    conn = await engine.acquire()
    +try:
    +    # play with the connection
    +finally:
    +    await conn.release()
    +
    +
    +

    where the connection should be manually returned to the pool with +conn.release().

    +

    Within the same context (usually the same Task, see +also 数据库事务), a nesting acquire by default re

    +
    +
    参数
    +
      +
    • timeout -- Block up to timeout seconds until there is one free +connection in the pool. Default is None - block forever until +succeeded. This has no effect when lazy=True, and depends on the +actual situation when reuse=True.

    • +
    • reuse -- Reuse the latest reusable acquired connection (before +it's returned to the pool) in current context if there is one, or +borrow a new one if none present. Default is False for always +borrow a new one. This is useful when you are in a nested method call +series, wishing to use the same connection without passing it around +as parameters. See also: 数据库事务. A reusing +connection is not reusable even if reusable=True. If the reused +connection happened to be a lazy one, then the reusing connection is +lazy too.

    • +
    • lazy -- Don't acquire the actual underlying connection yet - do it +only when needed. Default is False for always do it immediately. +This is useful before entering a code block which may or may not make +use of a given connection object. Feeding in a lazy connection will +save the borrow-return job if the connection is never used. If +setting reuse=True at the same time, then the reused connection - +if any - applies the same laziness. For example, reusing a lazy +connection with lazy=False will cause the reused connection to +acquire an underlying connection immediately.

    • +
    • reusable -- Mark this connection as reusable or otherwise. This +has no effect if it is a reusing connection. All reusable connections +are placed in a stack, any reusing acquire operation will always +reuse the top (latest) reusable connection. One reusable connection +may be reused by several reusing connections - they all share one +same underlying connection. Acquiring a connection with +reusable=False and reusing=False makes it a cleanly isolated +connection which is only referenced once here.

    • +
    +
    +
    返回
    +

    A GinoConnection object.

    +
    +
    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    Acquires a connection with reuse=True and runs +all() on it. reuse=True means you can safely +do this without borrowing more than one underlying connection:

    +
    async with engine.acquire():
    +    await engine.all('SELECT ...')
    +
    +
    +

    The same applies for other query methods.

    +
    + +
    +
    +async close()
    +

    Close the engine, by closing the underlying pool.

    +
    + +
    +
    +compile(clause, *multiparams, **params)
    +

    A shortcut for compile() on +the dialect, returns raw SQL string and parameters according to the +rules of the dialect.

    +
    + +
    +
    +connection_cls
    +

    Customizes the connection class to use, default is +GinoConnection.

    +

    GinoConnection 的别名

    +
    + +
    +
    +property current_connection
    +

    Gets the most recently acquired reusable connection in the context. +None if there is no such connection.

    +
    +
    返回
    +

    GinoConnection

    +
    +
    +
    + +
    +
    +property dialect
    +

    Read-only property for the +Dialect of this engine.

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs first(), See all().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    This requires that there is a reusable connection in the current +context, and an active transaction is present. Then its +GinoConnection.iterate() is executed and returned.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs one(), See all().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs one_or_none(), See all().

    +
    + +
    +
    +property raw_pool
    +

    Read-only access to the underlying database connection pool instance. +This depends on the actual dialect in use, Pool +of asyncpg for example.

    +
    + +
    +
    +repr(color=False)
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs scalar(), See all().

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs status(). See also all().

    +
    + +
    +
    +transaction(*args, timeout=None, reuse=True, reusable=True, **kwargs)
    +

    Borrows a new connection and starts a transaction with it.

    +

    Different to GinoConnection.transaction(), transaction on engine +level supports only managed usage:

    +
    async with engine.transaction() as tx:
    +    # play with transaction here
    +
    +
    +

    Where the implicitly acquired connection is available as +tx.connection.

    +

    By default, transaction() acquires connection with +reuse=True and reusable=True, that means it by default tries to +create a nested transaction instead of a new transaction on a new +connection. You can change the default behavior by setting these two +arguments.

    +

    The other arguments are the same as +transaction() on connection.

    + +
    +
    返回
    +

    A asynchronous context manager that yields a +GinoTransaction

    +
    +
    +
    + +
    +
    +update_execution_options(**opt)
    +

    Update the default execution_options dictionary +of this GinoEngine.

    + +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.exceptions.html b/docs/zh/1.0/reference/api/gino.exceptions.html new file mode 100644 index 0000000..30f3f2b --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.exceptions.html @@ -0,0 +1,241 @@ + + + + + + + + gino.exceptions module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.exceptions module

    +
    +
    +exception gino.exceptions.GinoException
    +

    基类:Exception

    +
    + +
    +
    +exception gino.exceptions.MultipleResultsFound
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoResultFound
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoSuchRowError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UninitializedError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UnknownJSONPropertyError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.ext.html b/docs/zh/1.0/reference/api/gino.ext.html new file mode 100644 index 0000000..4c050f9 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.ext.html @@ -0,0 +1,224 @@ + + + + + + + + gino.ext package - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.ext package

    +
    +

    Module contents

    +

    Namespace package for GINO extensions.

    +

    This namespace package didn't use any of the 3 official solutions. Instead, +gino.ext adds a hook into sys.meta_path, and utilize the Entry points +to find the extensions.

    +

    Any GINO extension package should provide an entry point like this:

    +
    [gino.extensions]
    +starlette = gino_starlette
    +
    +
    +

    So that the Python package gino_starlette will also be importable through +gino.ext.starlette.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.html b/docs/zh/1.0/reference/api/gino.html new file mode 100644 index 0000000..392f955 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.html @@ -0,0 +1,262 @@ + + + + + + + + gino package - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino package

    + + +
    +

    Module contents

    +
    +
    +gino.create_engine(*args, **kwargs)
    +

    Shortcut for sqlalchemy.create_engine() with strategy="gino".

    +
    + +
    +
    +gino.get_version()
    +

    Get current GINO version.

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.json_support.html b/docs/zh/1.0/reference/api/gino.json_support.html new file mode 100644 index 0000000..830c05e --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.json_support.html @@ -0,0 +1,347 @@ + + + + + + + + gino.json_support module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.json_support module

    +
    +
    +class gino.json_support.ArrayProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.BooleanProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.DateTimeProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.IntegerProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.JSONProperty(default=None, prop_name='profile')
    +

    基类:object

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +get_profile(instance)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    +
    +reload(instance)
    +
    + +
    +
    +save(instance, value=<object object>)
    +
    + +
    + +
    +
    +class gino.json_support.ObjectProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.StringProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.loader.html b/docs/zh/1.0/reference/api/gino.loader.html new file mode 100644 index 0000000..c7b2a1a --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.loader.html @@ -0,0 +1,630 @@ + + + + + + + + gino.loader module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.loader module

    +
    +
    +class gino.loader.AliasLoader(alias, *columns, **extras)
    +

    基类:gino.loader.ModelLoader

    +

    The same as ModelLoader, kept for compatibility.

    +
    + +
    +
    +class gino.loader.CallableLoader(func)
    +

    基类:gino.loader.Loader

    +

    Load the row by calling a specified function.

    +
    +
    参数
    +

    func -- A callable() taking 2 positional arguments (row, context) +that will be called in do_load(), returning the loaded result.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to the given function.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    The result calling the given function, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ColumnLoader(column)
    +

    基类:gino.loader.Loader

    +

    Load a given column in the row.

    +
    +
    参数
    +

    column -- The column name as str, or a +Column instance to avoid name conflict.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- Not used.

    • +
    +
    +
    返回
    +

    The value of the specified column, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.Loader
    +

    基类:object

    +

    The abstract base class of loaders.

    +

    Loaders are used to load raw database rows into expected results. +GinoEngine will use the loader set on the loader value of +the execution_options(), for example:

    +
    from sqlalchemy import text, create_engine
    +from gino.loader import ColumnLoader
    +
    +e = await create_engine("postgresql://localhost/gino", strategy="gino")
    +q = text("SELECT now() as ts")
    +loader = ColumnLoader("ts")
    +ts = await e.first(q.execution_options(loader=loader))  # datetime
    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    Must be implemented in subclasses.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    Any result that the loader is supposed to return, followed by a boolean +value indicating if the result is distinct.

    +
    +
    +
    + +
    +
    +classmethod get(value)
    +

    Automatically create a loader based on the type of the given value.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    value type

    loader type

    tuple

    TupleLoader

    callable()

    CallableLoader

    Column, +Label

    ColumnLoader

    Model

    ModelLoader

    Alias

    AliasLoader

    Loader

    as is

    any other types

    ValueLoader

    +
    +
    参数
    +

    value -- Any supported value above.

    +
    +
    返回
    +

    A loader instance.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +property query
    +

    Generate a query from this loader.

    +

    This is an experimental feature, not all loaders support this.

    +
    +
    返回
    +

    A query instance with the loader execution option set to self.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ModelLoader(model, *columns, **extras)
    +

    基类:gino.loader.Loader

    +

    A loader that loads a row into a GINO model instance.

    +

    This loader generates an instance of the given model type and fills the instance +with attributes according to the other given parameters:

    +
      +
    • Load each column attribute listed in the given columns positional arguments.

    • +
    • If columns is not given, all defined columns of the model will be loaded.

    • +
    • For each keyword argument, its value will be used to generate a loader using +Loader.get(), and the loaded value will be setattr() to the model +instance under the name of the key.

    • +
    +

    For example, the simplest select and load:

    +
    sqlalchemy.select([User]).execution_options(loader=ModelLoader(User))
    +
    +
    +

    Select only the name column, and still load it into model instances:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, User.name)
    +)
    +
    +
    +

    This would also yield User instances, with all column attributes as None but +name.

    +

    Nest a ValueLoader:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, id=1)
    +)
    +
    +
    +

    1 is then converted into a ValueLoader and mocked the id attribute +of all returned User instances as 1.

    +

    Nest another ModelLoader:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company)
    +)
    +
    +
    +

    Likewise, Company is converted into a ModelLoader to load the +Company columns from the joined result, and the Company instances are set to +the company attribute of each User instance using setattr().

    +
    +
    参数
    +
      +
    • model -- A subclass of Model to instantiate.

    • +
    • columns -- A list of Column or str to +load, default is all the columns in the model.

    • +
    • extras -- Additional attributes to load on the model instance.

    • +
    +
    +
    +
    +
    +distinct(*columns)
    +

    Configure this loader to reuse instances that have the same values of all the +give columns.

    +
    +
    参数
    +

    columns -- Preferably Column instances.

    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    The model instance, followed by a boolean value indicating if the +result is distinct.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +load(*columns, **extras)
    +

    Update the loader with new rules.

    +

    After initialization, the rules of this loader can still be updated. This is +useful when using the model class as a shortcut of ModelLoader where +possible, chaining with a load() to initialize the rules, for example:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company.load('name'))
    +)
    +
    +
    +
    +
    参数
    +
      +
    • columns -- If provided, replace the columns to load with the given ones.

    • +
    • extras -- Update the loader with new extras.

    • +
    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +none_as_none(enabled=True)
    +

    Deprecated method for compatibility, does nothing.

    +
    + +
    +
    +on(on_clause)
    +

    Specify the on_clause for generating joined queries.

    +

    This is an experimental feature, used by get_from().

    +
    +
    参数
    +

    on_clause -- An expression to feed into +join().

    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.TupleLoader(values)
    +

    基类:gino.loader.Loader

    +

    Load multiple values into a tuple.

    +
    +
    参数
    +

    values -- A tuple, each item is converted into a loader with +Loader.get().

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to sub-loaders.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    A tuple with loaded results from all sub-loaders, followed by +True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ValueLoader(value)
    +

    基类:gino.loader.Loader

    +

    A loader that always return the specified value.

    +
    +
    参数
    +

    value -- The value to return on load.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- Not used.

    • +
    • context -- Not used.

    • +
    +
    +
    返回
    +

    The given value, followed by True.

    +
    +
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.schema.html b/docs/zh/1.0/reference/api/gino.schema.html new file mode 100644 index 0000000..50f25f1 --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.schema.html @@ -0,0 +1,325 @@ + + + + + + + + gino.schema module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.schema module

    +
    +
    +class gino.schema.AsyncSchemaDropper(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    基类:gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaDropper

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, drop_ok=False)
    +
    + +
    +
    +async visit_table(table, drop_ok=False, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaGenerator(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    基类:gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaGenerator

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, create_ok=False)
    +
    + +
    +
    +async visit_table(table, create_ok=False, include_foreign_key_constraints=None, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaTypeMixin
    +

    基类:object

    +
    +
    +async create_async(bind=None, checkfirst=False)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncVisitor
    +

    基类:object

    +
    +
    +async traverse_single(obj, **kw)
    +
    + +
    + +
    +
    +class gino.schema.GinoSchemaVisitor(item)
    +

    基类:object

    +
    +
    +async create(bind=None, *args, **kwargs)
    +
    + +
    +
    +async create_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    +
    +async drop(bind=None, *args, **kwargs)
    +
    + +
    +
    +async drop_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    + +
    +
    +gino.schema.patch_schema(db)
    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.strategies.html b/docs/zh/1.0/reference/api/gino.strategies.html new file mode 100644 index 0000000..c964d6d --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.strategies.html @@ -0,0 +1,233 @@ + + + + + + + + gino.strategies module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.strategies module

    +
    +
    +class gino.strategies.GinoStrategy
    +

    基类:sqlalchemy.engine.strategies.EngineStrategy

    +

    A SQLAlchemy engine strategy for GINO.

    +

    This strategy is initialized automatically as gino is imported.

    +

    If sqlalchemy.create_engine() uses strategy="gino", it will return a +Coroutine, and treat URL prefix postgresql:// or +postgres:// as postgresql+asyncpg://.

    +
    +
    +async create(name_or_url, loop=None, **kwargs)
    +

    Given arguments, returns a new Engine instance.

    +
    + +
    +
    +engine_cls
    +

    gino.engine.GinoEngine 的别名

    +
    + +
    +
    +name = 'gino'
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/api/gino.transaction.html b/docs/zh/1.0/reference/api/gino.transaction.html new file mode 100644 index 0000000..649f73a --- /dev/null +++ b/docs/zh/1.0/reference/api/gino.transaction.html @@ -0,0 +1,329 @@ + + + + + + + + gino.transaction module - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.transaction module

    +
    +
    +class gino.transaction.GinoTransaction(conn, args, kwargs)
    +

    基类:object

    +

    Represents an underlying database transaction and its connection, offering +methods to manage this transaction.

    +

    GinoTransaction is supposed to be created by either +gino.engine.GinoConnection.transaction(), or +gino.engine.GinoEngine.transaction(), or +gino.api.Gino.transaction(), shown as follows:

    +
    async with db.transaction() as tx:
    +    ...
    +
    +async with engine.transaction() as tx:
    +    ...
    +
    +async with conn.transaction() as tx:
    +    ...
    +
    +tx = await conn.transaction()
    +try:
    +    ...
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    When in use with asynchronous context manager, GinoTransaction +will be in managed mode, while the last example with await will put +the GinoTransaction in manual mode where you have to call +the commit() or rollback() to manually close the transaction.

    +

    In managed mode the transaction will be automatically committed or +rolled back on exiting the async with block depending on whether there +is an exception or not. Meanwhile, you can explicitly exit the transaction +early by raise_commit() or raise_rollback() which will raise +an internal exception managed by the asynchronous context manager and +interpreted as a commit or rollback action. In a nested transaction +situation, the two exit-early methods always close up the very transaction +which the two methods are referenced upon - all children transactions are +either committed or rolled back correspondingly, while no parent +transaction was ever touched. For example:

    +
    async with db.transaction() as tx1:
    +    async with db.transaction() as tx2:
    +        async with db.transaction() as tx3:
    +            tx2.raise_rollback()
    +            # Won't reach here
    +        # Won't reach here
    +    # Continues here with tx1, with both tx2 and tx3 rolled back.
    +
    +    # For PostgreSQL, tx1 can still be committed successfully because
    +    # tx2 and tx3 are just SAVEPOINTs in transaction tx1
    +
    +
    +
    +

    小技巧

    +

    The internal exception raised from raise_commit() and +raise_rollback() is a subclass of BaseException, so +normal try ... except Exception: can't trap the commit or rollback.

    +
    +
    +
    +async commit()
    +

    Only available in manual mode: manually commit this transaction.

    +
    + +
    +
    +property connection
    +

    Accesses to the GinoConnection of this +transaction. This is useful if when the transaction is started from +db or engine where the connection is implicitly acquired for +you together with the transaction.

    +
    + +
    +
    +raise_commit()
    +

    Only available in managed mode: skip rest of the code in this +transaction and commit immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    async with db.transaction() as tx:
    +    await user.update(age=64).apply()
    +    tx.raise_commit()
    +    await user.update(age=32).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +raise_rollback()
    +

    Only available in managed mode: skip rest of the code in this +transaction and rollback immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    assert user.age == 64  # assumption
    +
    +async with db.transaction() as tx:
    +    await user.update(age=32).apply()
    +    tx.raise_rollback()
    +    await user.update(age=128).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +property raw_transaction
    +

    Accesses to the underlying transaction object, whose type depends on +the dialect in use.

    +
    + +
    +
    +async rollback()
    +

    Only available in manual mode: manually rollback this transaction.

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/extensions.html b/docs/zh/1.0/reference/extensions.html new file mode 100644 index 0000000..0a84017 --- /dev/null +++ b/docs/zh/1.0/reference/extensions.html @@ -0,0 +1,217 @@ + + + + + + + + 扩展 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/extensions/sanic.html b/docs/zh/1.0/reference/extensions/sanic.html new file mode 100644 index 0000000..8b478f4 --- /dev/null +++ b/docs/zh/1.0/reference/extensions/sanic.html @@ -0,0 +1,326 @@ + + + + + + + + Sanic Support - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/extensions/starlette.html b/docs/zh/1.0/reference/extensions/starlette.html new file mode 100644 index 0000000..e78d54b --- /dev/null +++ b/docs/zh/1.0/reference/extensions/starlette.html @@ -0,0 +1,323 @@ + + + + + + + + Starlette Support - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    注解

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/extensions/tornado.html b/docs/zh/1.0/reference/extensions/tornado.html new file mode 100644 index 0000000..e9c22e9 --- /dev/null +++ b/docs/zh/1.0/reference/extensions/tornado.html @@ -0,0 +1,205 @@ + + + + + + + + Tornado Support - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/reference/history.html b/docs/zh/1.0/reference/history.html new file mode 100644 index 0000000..e9a9ec2 --- /dev/null +++ b/docs/zh/1.0/reference/history.html @@ -0,0 +1,900 @@ + + + + + + + + 版本历史 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    版本历史

    +
    +

    GINO 1.0

    +
    +

    Migrating to GINO 1.0

    +

    GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +

    Extension Module

    Installation in GINO 1.0

    gino.ext.starlette

    pip install gino[starlette]

    gino.ext.aiohttp

    pip install gino[aiohttp]

    gino.ext.sanic

    pip install gino[sanic]

    gino.ext.tornado

    pip install gino[tornado]

    gino.ext.quart

    pip install gino[quart]

    +

    The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed gino[starlette]:

    +
    from gino.ext.starlette import Gino
    +
    +
    +

    GINO 1.0 switched to Poetry for package and +dependency management and started to use the +src layout. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process:

    +
      +
    • Source files are now located under src directory.

    • +
    • The src dist on PyPI does not include tests, docs and some other files due to +a limitation of Poetry.

    • +
    +
    +
    +

    1.0.1 (2020-06-08)

    +
      +
    • Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4)

    • +
    • Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672)

    • +
    • Multiple JSON property fixes (#661 #662 #695)

    • +
    • Fixed extension typing issue (#673 #674)

    • +
    • Fixed model override behavior (#694)

    • +
    • Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696)

    • +
    +
    +
    +

    1.0.0 (2020-04-26)

    +
      +
    • Switched to Poetry for package and dependency management.

    • +
    • [Breaking] Moved built-in extension modules to separate PyPI packages.

    • +
    • Switched to src layout.

    • +
    • Switched to black code style.

    • +
    • Better documentation.

    • +
    • [Breaking] none_as_none() is now always enabled.

    • +
    • Added representation method for engine.

    • +
    • Protected the URL instance fed to set_bind() from manipulation.

    • +
    • Replaced some assert with AssertionError (#258 #655)

    • +
    +
    +
    +
    +

    GINO 0.8

    +

    This is also version 1.0 release candidate.

    +
    +

    Migrating to GINO 0.8

    +
    +

    1. contextvars

    +

    We introduced aiocontextvars 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the enable_inherit() or +disable_inherit() calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created after importing gino or aiocontextvars, or the patch +won't work correctly.

    +

    There is nothing to worry about in Python 3.7.

    +
    +
    +

    2. none_as_none

    +

    When GINO tries to load a row with all NULL values into an instance, it +will now by default return None instead of an instance with all None +attributes. To recover the default behavior of 0.7, please specify +none_as_none(False) in affected model loader.

    +

    This is especially applicable to relationship sub-loaders - if the sub-loader +found it all NULL, no instance will be set to parent instance. For +example:

    +
    child = await Child.load(parent=Parent).query.gino.first()
    +
    +
    +

    If child.parent_id is NULL in database, then the child instance +won't be called with any setattr(child, 'parent', ...) at all. (If you need +child.parent == None in this case, consider setting default value +parent = None in child model.)

    +

    Please note, it is deprecated to disable none_as_none, and disabling will +be removed in GINO 1.0.

    +
    +
    +
    +

    0.8.7 (2020-04-19)

    +
      +
    • Improved error handling when attribute names collide (Contributed by Reskov in #637 #638)

    • +
    • Fixed with_bind usability in aiohttp extension (#518)

    • +
    +
    +
    +

    0.8.6 (2020-02-10)

    +
      +
    • Fixed JSONPathType bind processor for asyncpg (#609)

    • +
    • Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600)

    • +
    • Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629)

    • +
    • Added distinct() to Alias (#628)

    • +
    +
    +
    +

    0.8.5 (2019-11-19)

    +
      +
    • Improved support for __tablename__ in declared_attr (Contributed by Roald Storm in #592)

    • +
    +
    +
    +

    0.8.4 (2019-11-09)

    +
      +
    • Better loader support for models in subqueries (#573 #585)

    • +
    • Allowed __tablename__ to be a declared_attr (#579 #582)

    • +
    • Fixed Sanic 19.9.0 compatibility (#569)

    • +
    • Added one() and one_or_none() (Contributed by Ilaï Deutel in #577)

    • +
    • Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538)

    • +
    • Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533)

    • +
    • Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520)

    • +
    • Fixed Grammar (Contributed by Simeon J Morgan in #504)

    • +
    +
    +
    +

    0.8.3 (2019-06-06)

    +
      +
    • Fixed deprecated warnings in asyncpg dialect and aiohttp (#425)

    • +
    • Customizable db attribute name in aiohttp app instance (#457)

    • +
    • Added Starlette support (#486)

    • +
    +
    +
    +

    0.8.2 (2019-03-07)

    +
      +
    • Added exception for unknown JSON properties (#406 #408)

    • +
    • Supported Quart 0.7 (#411)

    • +
    • Accepted kwargs for db init in extensions (#407 #427)

    • +
    • Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440)

    • +
    • Added NullPool (#437 #441)

    • +
    • Unpinned dependency versions (#447)

    • +
    • Added support for SQLAlchemy 1.3 (#433 #451)

    • +
    +
    +
    +

    0.8.1 (2018-12-08)

    +
      +
    • Alias supported Label (#365)

    • +
    • Docs update (#308, 4c59ad, #401 by Pascal van Kooten)

    • +
    • Version requirement for SQLAlchemy is updated to >=1.2 (#378 #382)

    • +
    • Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) +* And all other extensions (#395)

    • +
    • Supported Tornado 5 (#396, also thanks to Vladimir Goncharov)

    • +
    • Fixed custom JSON/JSONB type support (#402 #403)

    • +
    +

    (Most fixes done by Tony Wang)

    +
    +
    +

    0.8.0 (2018-10-16)

    +
      +
    • Welcome Tony Wang to the maintenance team (#335)

    • +
    • Allowed custom column names (#261 #297)

    • +
    • Allowed column instance in model.load() (Contributed by Jekel in #323)

    • +
    • [Breaking] Upgraded to aiocontextvars 0.2.0 (#333)

    • +
    • Fixed bug that the same empty stack is shared between sub-tasks (#313 #334)

    • +
    • [Breaking] Made none_as_none() the default behavior (#351)

    • +
    • Bug fixes and docs update

    • +
    +
    +
    +
    +

    GINO 0.7

    +

    This is also version 1.0 beta 3.

    +
    +

    0.7.7 (2018-12-08)

    +
      +
    • Backported fix for custom JSON/JSONB type support (#402 #403)

    • +
    +
    +
    +

    0.7.6 (2018-08-26)

    +
      +
    • Updated library support (Contributed by Tony Wang in #275 #309)

    • +
    • Added none_as_none() (#281 #282)

    • +
    • Added ARRAY alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289)

    • +
    • Added Model.lookup() to prevent updating whole table without primary key (#287 #288)

    • +
    • Added DB_ECHO in extension options (Contributed by Mykyta Holubakha in #298)

    • +
    • Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305)

    • +
    • Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304)

    • +
    • Fixed to raise UninitializedError if bind is None (Contributed by Tony Wang in #307 #310)

    • +
    +
    +
    +

    0.7.5 (2018-07-26)

    +
      +
    • Added friendly error message when using abstract models by mistake (#224)

    • +
    • Supported Python 3.7 (Contributed by Tony Wang in #265)

    • +
    • Updated documentation

    • +
    • Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280)

    • +
    +
    +
    +

    0.7.4 (2018-06-10)

    +
      +
    • Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228)

    • +
    • Added Quart support (#213)

    • +
    • Fixed Tornado options parsing (#231)

    • +
    • Improved coding style and test coverage

    • +
    +
    +
    +

    0.7.3 (2018-05-19)

    +
      +
    • Fix for failing binary type (#225)

    • +
    +
    +
    +

    0.7.2 (2018-05-15)

    +
      +
    • Added prepared statement support (#14)

    • +
    • Added dsn in extension config (Contributed by Yurii Shtrikker in #215)

    • +
    +
    +
    +

    0.7.1 (2018-05-03)

    +
      +
    • Added support for inline model constraints (Contributed by Kinware in #198)

    • +
    • Added docs and tests for using SSL (#202)

    • +
    • Added declared_attr (#204)

    • +
    • Allowed ModelLoader passively load partial model (#216)

    • +
    +
    +
    +

    0.7.0 (2018-04-18)

    +
      +
    • Added Python 3.5 support (#187)

    • +
    • Added support to use dict as ident for Model.get (#192)

    • +
    • Added result loader (partial relationship support) (#13)

    • +
    • Added documentation on relationship and transaction (#146)

    • +
    +
    +
    +
    +

    GINO 0.6

    +

    This is also version 1.0 beta 2.

    +
    +

    Migrating to GINO 0.6

    +
    +

    1. Task Local

    +

    We created a new Python package aiocontextvars from previous local.py. If +you made use of the task local features, you should install this package.

    +

    Previous gino.enable_task_local() and gino.disable_task_local() are +replaced by aiocontextvars.enable_inherit() and +aiocontextvars.disable_inherit(). However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars by default offers task +local even without enable_inherit(), which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars is installed.

    +

    There is no gino.get_local() and gino.reset_local() relevant in +aiocontextvars. The similar thing is aiocontextvars.ContextVar instance +through its get(), set() and delete() methods.

    +

    Previous gino.is_local_root() is now +not aiocontextvars.Context.current().inherited.

    +
    +
    +

    2. Engine

    +

    GINO 0.6 hides asyncpg.Pool behind the new SQLAlchemy-alike +gino.GinoEngine. Instead of doing this in 0.5:

    +
    async with db.create_pool('postgresql://...') as pool:
    +    # your code here
    +
    +
    +

    You should change it to this in 0.6:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +

    This equals to:

    +
    engine = await gino.create_engine('postgresql://...')
    +db.bind = engine
    +try:
    +    # your code here
    +finally:
    +    db.bind = None
    +    await engine.close()
    +
    +
    +

    Or:

    +
    engine = await db.set_bind('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Or even this:

    +
    db = await gino.Gino('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Choose whichever suits you the best.

    +

    Obviously GinoEngine doesn't provide asyncpg.Pool methods directly any +longer, but you can get the underlying asyncpg.Pool object through +engine.raw_pool property.

    +

    GinoPool.get_current_connection() is now changed to current_connection +property on GinoEngine instances to support multiple engines.

    +

    GinoPool.execution_option is gone, instead update_execution_options() +on GinoEngine instance is available.

    +

    GinoPool().metadata is gone, dialect is still available.

    +

    GinoPool.release() is removed in GinoEngine and Gino, the +release() method on GinoConnection object should be used instead.

    +

    These methods exist both in 0.5 GinoPool and 0.6 GinoEngine: +close(), acquire(), all(), first(), scalar(), status().

    +
    +
    +

    3. GinoConnection

    +

    Similarly, GinoConnection in 0.6 is no longer a subclass of +asyncpg.Connection, instead it has a asyncpg.Connection instance, +accessable through GinoConnection.raw_connection property.

    +

    GinoConnection.metadata is deleted in 0.6, while dialect remained.

    +

    GinoConnection.execution_options() is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior.

    +

    GinoConnection.release() is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +permanent=False to remain its previous behavior.

    +

    And all(), first(), scalar(), status(), iterate(), +transaction() remained in 0.6.

    +
    +
    +

    4. Query API

    +

    All five query APIs all(), first(), scalar(), status(), +iterate() now accept the same parameters as SQLAlchemy execute(), +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter bind if they did. Just use the API on the GinoEngine or +GinoConnection object instead.

    +
    +
    +

    5. Transaction

    +

    Transaction interface is rewritten. Now in 0.6, a GinoTransaction object is +provided consistently from all 3 methods:

    +
    async with db.transaction() as tx:
    +    # within transaction
    +
    +async with engine.transaction() as tx:
    +    # within transaction
    +
    +async with engine.acquire() as conn:
    +    async with conn.transaction() as tx:
    +        # within transaction
    +
    +
    +

    And different usage with await:

    +
    tx = await db.transaction()
    +try:
    +    # within transaction
    +    await tx.commit()
    +except:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    The GinoConnection object is available at tx.connection, while +underlying transaction object from database driver is available at +tx.transaction - for asyncpg it is an asyncpg.transaction.Transaction +object.

    +
    +
    +
    +

    0.6.6 (2018-05-18)

    +
      +
    • Backported a fix for failing binary type (#225)

    • +
    +
    +
    +

    0.6.5 (2018-04-18)

    +
      +
    • Abandoned 0.6.4 and keep 0.6.x stable

    • +
    • Backported doc for transaction

    • +
    +
    +
    +

    0.6.4 (2018-04-16)

    +

    Abandoned version, please use 0.7.0 instead.

    +
    +
    +

    0.6.3 (2018-04-08)

    +
      +
    • Added aiohttp support

    • +
    • Added support for calling create() on model instances (Contributed by Kinware in #178 #180)

    • +
    • Fixed get() by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184)

    • +
    +
    +
    +

    0.6.2 (2018-03-24)

    +
      +
    • Fixed SQLAlchemy prefetch issue (#141)

    • +
    • Fixed issue that mixin class on Model not working (#174)

    • +
    • Added more documentation (Thanks Olaf Conradi for reviewing)

    • +
    +
    +
    +

    0.6.1 (2018-03-18)

    +
      +
    • Fixed create and drop for Enum type (#160)

    • +
    • A bit more documentation (#159)

    • +
    +
    +
    +

    0.6.0 (2018-03-14)

    +
      +
    • [Breaking] API Refactored, Pool replaced with Engine

      +
        +
      • New API Engine replaced asyncpg Pool (#59)

      • +
      • Supported different dialects, theoretically

      • +
      • Used aiocontextvars instead of builtin task local (#89)

      • +
      +
    • +
    • [Breaking] Fixed query API with multiparams (executemany) to return correctly (#20)

    • +
    • [Breaking] The query methods no longer accept the parameter bind

    • +
    • [Breaking] Gino no longer exposes postgresql types

    • +
    • Added echo on engine (#142)

    • +
    • Added tests to cover 80% of code

    • +
    • Added gino extension on SchemaItem for create_all and so on (#76 #106)

    • +
    • Added gino extension on model classes for create() or drop()

    • +
    • Added _update_request_cls on CRUDModel (#147)

    • +
    • Rewrote the documentation (#146)

    • +
    +
    +
    +
    +

    GINO 0.5

    +

    This is also version 1.0 beta 1.

    +
    +

    0.5.8 (2018-02-14)

    +
      +
    • Preparing for 0.6.0 which will be a breaking release

    • +
    • Fixed wrong value of Enum in creation (Contributed by Sergey Kovalev in #126)

    • +
    +
    +
    +

    0.5.7 (2017-11-24)

    +

    This is an emergency fix for 0.5.6.

    +
      +
    • Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114)

    • +
    • Added Model.outerjoin

    • +
    +
    +
    +

    0.5.6 (2017-11-23)

    +
      +
    • Changed to use unnamed statement when possible (#80 #90)

    • +
    • Added more example (Contributed by Kentoseth in #109)

    • +
    • Added Model.join and made Model selectable (Contributed by Ádám Barancsuk in #112 #113)

    • +
    +
    +
    +

    0.5.5 (2017-10-18)

    +
      +
    • Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87)

    • +
    • Added ability to reset local storage (#84)

    • +
    • Fixed bug in JSON property update

    • +
    • Added update chaining feature

    • +
    +
    +
    +

    0.5.4 (2017-10-04)

    +
      +
    • Updated example (Contributed by Kinware in #75)

    • +
    • Added Model.insert (Contributed by Neal Wang in #63)

    • +
    • Fixed issue that non-lazy acquiring fails dirty (#79)

    • +
    +
    +
    +

    0.5.3 (2017-09-23)

    +
      +
    • Fixed no module named cutils error (Contributed by Vladimir Goncharov in #73)

    • +
    +
    +
    +

    0.5.2 (2017-09-10)

    +
      +
    • Added missing driver name on dialect (#67)

    • +
    • Fixed dialect to support native decimal type (#67)

    • +
    +
    +
    +

    0.5.1 (2017-09-09)

    +

    This is an emergency fix for 0.5.0.

    +
      +
    • Reverted the extension, back to pure Python (#60)

    • +
    • Used SQLAlchemy RowProxy

    • +
    • Added first_or_404

    • +
    • Fixed bug that GinoPool cannot be inherited

    • +
    +
    +
    +

    0.5.0 (2017-09-03)

    +
      +
    • [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten

      +
        +
      • Extracted CRUD operations

      • +
      • Core operations are moved to dialect and execution context

      • +
      • Removed guess_model, switched to explicit execution options

      • +
      • Turned timeout parameter to an execution option

      • +
      • Extracted pool, connection and api from asyncpg_delegate

      • +
      +
    • +
    • Added support for SQLAlchemy execution options, and a few custom options

    • +
    • [Breaking] Made Model.select return rows by default (#39)

    • +
    • Moved get_or_404 to extensions (#38)

    • +
    • Added iterator on model classes (#43)

    • +
    • Added Tornado extension (Contributed by Vladimir Goncharov)

    • +
    • Added Model.to_dict (#47)

    • +
    • Added an extension module to update asyncpg.Record with processed results

    • +
    +
    +
    +
    +

    Early Development Releases

    +

    Considered as alpha releases.

    +
    +

    0.4.1 (2017-08-20)

    +
      +
    • Support select on model instance

    • +
    +
    +
    +

    0.4.0 (2017-08-15)

    +
      +
    • Made get_or_404 more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31)

    • +
    • Delegated sqlalchemy.__all__ (Contributed by Neal Wang in #10 #33)

    • +
    • [Breaking] Rewrote JSON/JSONB support (#29)

    • +
    • Added lazy parameter on db.acquire (Contributed by Binghan Li in #32)

    • +
    • Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34)

    • +
    • Fixed iterate API to be compatible with asyncpg (#32)

    • +
    • Unified exceptions

    • +
    • [Breaking] Changed update API (#29)

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.3.0 (2017-08-07)

    +
      +
    • Supported __table_args__ (#12)

    • +
    • Introduced task local to manage connection in context (#19)

    • +
    • Added query.gino extension for in-place execution

    • +
    • Refreshed README (#3)

    • +
    • Adopted PEP 487 (Contributed by Tony Wang in #17 #27)

    • +
    • Used weakref on __model__ of table and query (Contributed by Tony Wang)

    • +
    • Delegated asyncpg timeout parameter (Contributed by Neal Wang in #16 #22)

    • +
    +
    +
    +

    0.2.3 (2017-08-04)

    +
      +
    • Supported any primary key (Contributed by Tony Wang in #11)

    • +
    +
    +
    +

    0.2.2 (2017-08-02)

    +
      +
    • Supported SQLAlchemy result processor

    • +
    • Added rich support on JSON/JSONB

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.2.1 (2017-07-28)

    +
      +
    • Added update and delete API

    • +
    +
    +
    +

    0.2.0 (2017-07-28)

    +
      +
    • Changed API, no longer reuses asyncpg API

    • +
    +
    +
    +

    0.1.1 (2017-07-25)

    +
      +
    • Added db.bind

    • +
    • API changed: parameter conn renamed to optional bind

    • +
    • Delegated asyncpg Pool with db.create_pool

    • +
    • Internal enhancement and bug fixes

    • +
    +
    +
    +

    0.1.0 (2017-07-21)

    +
      +
    • First release on PyPI.

    • +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/search.html b/docs/zh/1.0/search.html new file mode 100644 index 0000000..2500d6c --- /dev/null +++ b/docs/zh/1.0/search.html @@ -0,0 +1 @@ +

    search.html

    \ No newline at end of file diff --git a/docs/zh/1.0/searchindex.js b/docs/zh/1.0/searchindex.js new file mode 100644 index 0000000..468ece3 --- /dev/null +++ b/docs/zh/1.0/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["explanation","explanation/async","explanation/engine","explanation/why","how-to","how-to/alembic","how-to/contributing","how-to/crud","how-to/faq","how-to/json-props","how-to/loaders","how-to/pool","how-to/schema","how-to/transaction","index","reference","reference/api","reference/api/gino","reference/api/gino.aiocontextvars","reference/api/gino.api","reference/api/gino.crud","reference/api/gino.declarative","reference/api/gino.dialects","reference/api/gino.dialects.asyncpg","reference/api/gino.dialects.base","reference/api/gino.engine","reference/api/gino.exceptions","reference/api/gino.ext","reference/api/gino.json_support","reference/api/gino.loader","reference/api/gino.schema","reference/api/gino.strategies","reference/api/gino.transaction","reference/extensions","reference/extensions/sanic","reference/extensions/starlette","reference/extensions/tornado","reference/history","tutorials","tutorials/announcement","tutorials/fastapi","tutorials/tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["explanation.rst","explanation/async.rst","explanation/engine.rst","explanation/why.rst","how-to.rst","how-to/alembic.rst","how-to/contributing.rst","how-to/crud.rst","how-to/faq.rst","how-to/json-props.rst","how-to/loaders.rst","how-to/pool.rst","how-to/schema.rst","how-to/transaction.rst","index.rst","reference.rst","reference/api.rst","reference/api/gino.rst","reference/api/gino.aiocontextvars.rst","reference/api/gino.api.rst","reference/api/gino.crud.rst","reference/api/gino.declarative.rst","reference/api/gino.dialects.rst","reference/api/gino.dialects.asyncpg.rst","reference/api/gino.dialects.base.rst","reference/api/gino.engine.rst","reference/api/gino.exceptions.rst","reference/api/gino.ext.rst","reference/api/gino.json_support.rst","reference/api/gino.loader.rst","reference/api/gino.schema.rst","reference/api/gino.strategies.rst","reference/api/gino.transaction.rst","reference/extensions.rst","reference/extensions/sanic.rst","reference/extensions/starlette.rst","reference/extensions/tornado.rst","reference/history.rst","tutorials.rst","tutorials/announcement.rst","tutorials/fastapi.rst","tutorials/tutorial.rst"],objects:{"":{gino:[17,0,0,"-"]},"gino.aiocontextvars":{patch_asyncio:[18,1,1,""]},"gino.api":{Gino:[19,2,1,""],GinoExecutor:[19,2,1,""]},"gino.api.Gino":{Model:[19,3,1,""],acquire:[19,3,1,""],all:[19,3,1,""],bind:[19,3,1,""],compile:[19,3,1,""],first:[19,3,1,""],iterate:[19,3,1,""],model_base_classes:[19,4,1,""],no_delegate:[19,4,1,""],one:[19,3,1,""],one_or_none:[19,3,1,""],pop_bind:[19,3,1,""],query_executor:[19,4,1,""],scalar:[19,3,1,""],schema_visitor:[19,4,1,""],set_bind:[19,3,1,""],status:[19,3,1,""],transaction:[19,3,1,""],with_bind:[19,3,1,""]},"gino.api.GinoExecutor":{all:[19,3,1,""],first:[19,3,1,""],iterate:[19,3,1,""],load:[19,3,1,""],model:[19,3,1,""],one:[19,3,1,""],one_or_none:[19,3,1,""],query:[19,3,1,""],return_model:[19,3,1,""],scalar:[19,3,1,""],status:[19,3,1,""],timeout:[19,3,1,""]},"gino.crud":{Alias:[20,2,1,""],CRUDModel:[20,2,1,""],QueryModel:[20,2,1,""],UpdateRequest:[20,2,1,""]},"gino.crud.Alias":{distinct:[20,3,1,""],load:[20,3,1,""],on:[20,3,1,""]},"gino.crud.CRUDModel":{"delete":[20,4,1,""],alias:[20,3,1,""],append_where_primary_key:[20,3,1,""],create:[20,3,1,""],distinct:[20,3,1,""],get:[20,3,1,""],in_query:[20,3,1,""],load:[20,3,1,""],lookup:[20,3,1,""],none_as_none:[20,3,1,""],on:[20,3,1,""],query:[20,4,1,""],select:[20,3,1,""],to_dict:[20,3,1,""],update:[20,4,1,""]},"gino.crud.UpdateRequest":{apply:[20,3,1,""],update:[20,3,1,""]},"gino.declarative":{ColumnAttribute:[21,2,1,""],InvertDict:[21,2,1,""],Model:[21,2,1,""],declarative_base:[21,1,1,""],declared_attr:[21,1,1,""]},"gino.declarative.InvertDict":{invert_get:[21,3,1,""]},"gino.dialects":{asyncpg:[23,0,0,"-"],base:[24,0,0,"-"]},"gino.dialects.asyncpg":{AsyncEnum:[23,2,1,""],AsyncpgCompiler:[23,2,1,""],AsyncpgCursor:[23,2,1,""],AsyncpgDBAPI:[23,2,1,""],AsyncpgDialect:[23,2,1,""],AsyncpgExecutionContext:[23,2,1,""],AsyncpgIterator:[23,2,1,""],AsyncpgJSONPathType:[23,2,1,""],DBAPICursor:[23,2,1,""],GinoNullType:[23,2,1,""],NullPool:[23,2,1,""],Pool:[23,2,1,""],PreparedStatement:[23,2,1,""],Transaction:[23,2,1,""]},"gino.dialects.asyncpg.AsyncEnum":{create_async:[23,3,1,""],drop_async:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgCompiler":{bindtemplate:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgCursor":{forward:[23,3,1,""],many:[23,3,1,""],next:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgDBAPI":{Error:[23,4,1,""]},"gino.dialects.asyncpg.AsyncpgDialect":{colspecs:[23,4,1,""],cursor_cls:[23,4,1,""],dbapi_class:[23,4,1,""],driver:[23,4,1,""],execution_ctx_cls:[23,4,1,""],get_isolation_level:[23,3,1,""],has_schema:[23,3,1,""],has_sequence:[23,3,1,""],has_table:[23,3,1,""],has_type:[23,3,1,""],init_kwargs:[23,4,1,""],init_pool:[23,3,1,""],on_connect:[23,3,1,""],set_isolation_level:[23,3,1,""],statement_compiler:[23,4,1,""],supports_native_decimal:[23,4,1,""],transaction:[23,3,1,""]},"gino.dialects.asyncpg.AsyncpgJSONPathType":{bind_processor:[23,3,1,""]},"gino.dialects.asyncpg.DBAPICursor":{async_execute:[23,3,1,""],description:[23,3,1,""],get_statusmsg:[23,3,1,""],prepare:[23,3,1,""]},"gino.dialects.asyncpg.GinoNullType":{result_processor:[23,3,1,""]},"gino.dialects.asyncpg.NullPool":{acquire:[23,3,1,""],close:[23,3,1,""],raw_pool:[23,3,1,""],release:[23,3,1,""],repr:[23,3,1,""]},"gino.dialects.asyncpg.Pool":{acquire:[23,3,1,""],close:[23,3,1,""],raw_pool:[23,3,1,""],release:[23,3,1,""],repr:[23,3,1,""]},"gino.dialects.asyncpg.Transaction":{begin:[23,3,1,""],commit:[23,3,1,""],raw_transaction:[23,3,1,""],rollback:[23,3,1,""]},"gino.dialects.base":{AsyncDialectMixin:[24,2,1,""],BaseDBAPI:[24,2,1,""],Cursor:[24,2,1,""],DBAPICursor:[24,2,1,""],ExecutionContextOverride:[24,2,1,""],Pool:[24,2,1,""],PreparedStatement:[24,2,1,""],Transaction:[24,2,1,""]},"gino.dialects.base.AsyncDialectMixin":{compile:[24,3,1,""],cursor_cls:[24,4,1,""],dbapi:[24,3,1,""],dbapi_class:[24,4,1,""],init_pool:[24,3,1,""],transaction:[24,3,1,""]},"gino.dialects.base.BaseDBAPI":{Binary:[24,3,1,""],Error:[24,4,1,""],paramstyle:[24,4,1,""]},"gino.dialects.base.Cursor":{forward:[24,3,1,""],many:[24,3,1,""],next:[24,3,1,""]},"gino.dialects.base.DBAPICursor":{async_execute:[24,3,1,""],description:[24,3,1,""],execute:[24,3,1,""],executemany:[24,3,1,""],get_statusmsg:[24,3,1,""],prepare:[24,3,1,""]},"gino.dialects.base.ExecutionContextOverride":{get_result_proxy:[24,3,1,""],loader:[24,4,1,""],model:[24,4,1,""],process_rows:[24,3,1,""],return_model:[24,4,1,""],timeout:[24,4,1,""]},"gino.dialects.base.Pool":{acquire:[24,3,1,""],close:[24,3,1,""],raw_pool:[24,3,1,""],release:[24,3,1,""],repr:[24,3,1,""]},"gino.dialects.base.PreparedStatement":{all:[24,3,1,""],first:[24,3,1,""],iterate:[24,3,1,""],scalar:[24,3,1,""],status:[24,3,1,""]},"gino.dialects.base.Transaction":{begin:[24,3,1,""],commit:[24,3,1,""],raw_transaction:[24,3,1,""],rollback:[24,3,1,""]},"gino.engine":{GinoConnection:[25,2,1,""],GinoEngine:[25,2,1,""]},"gino.engine.GinoConnection":{all:[25,3,1,""],dialect:[25,3,1,""],execution_options:[25,3,1,""],first:[25,3,1,""],get_raw_connection:[25,3,1,""],iterate:[25,3,1,""],one:[25,3,1,""],one_or_none:[25,3,1,""],prepare:[25,3,1,""],raw_connection:[25,3,1,""],release:[25,3,1,""],scalar:[25,3,1,""],schema_for_object:[25,4,1,""],status:[25,3,1,""],transaction:[25,3,1,""]},"gino.engine.GinoEngine":{acquire:[25,3,1,""],all:[25,3,1,""],close:[25,3,1,""],compile:[25,3,1,""],connection_cls:[25,4,1,""],current_connection:[25,3,1,""],dialect:[25,3,1,""],first:[25,3,1,""],iterate:[25,3,1,""],one:[25,3,1,""],one_or_none:[25,3,1,""],raw_pool:[25,3,1,""],repr:[25,3,1,""],scalar:[25,3,1,""],status:[25,3,1,""],transaction:[25,3,1,""],update_execution_options:[25,3,1,""]},"gino.exceptions":{GinoException:[26,5,1,""],MultipleResultsFound:[26,5,1,""],NoResultFound:[26,5,1,""],NoSuchRowError:[26,5,1,""],UninitializedError:[26,5,1,""],UnknownJSONPropertyError:[26,5,1,""]},"gino.json_support":{ArrayProperty:[28,2,1,""],BooleanProperty:[28,2,1,""],DateTimeProperty:[28,2,1,""],IntegerProperty:[28,2,1,""],JSONProperty:[28,2,1,""],ObjectProperty:[28,2,1,""],StringProperty:[28,2,1,""]},"gino.json_support.ArrayProperty":{decode:[28,3,1,""],encode:[28,3,1,""]},"gino.json_support.BooleanProperty":{decode:[28,3,1,""],encode:[28,3,1,""],make_expression:[28,3,1,""]},"gino.json_support.DateTimeProperty":{decode:[28,3,1,""],encode:[28,3,1,""],make_expression:[28,3,1,""]},"gino.json_support.IntegerProperty":{decode:[28,3,1,""],encode:[28,3,1,""],make_expression:[28,3,1,""]},"gino.json_support.JSONProperty":{decode:[28,3,1,""],encode:[28,3,1,""],get_profile:[28,3,1,""],make_expression:[28,3,1,""],reload:[28,3,1,""],save:[28,3,1,""]},"gino.json_support.ObjectProperty":{decode:[28,3,1,""],encode:[28,3,1,""]},"gino.json_support.StringProperty":{make_expression:[28,3,1,""]},"gino.loader":{AliasLoader:[29,2,1,""],CallableLoader:[29,2,1,""],ColumnLoader:[29,2,1,""],Loader:[29,2,1,""],ModelLoader:[29,2,1,""],TupleLoader:[29,2,1,""],ValueLoader:[29,2,1,""]},"gino.loader.CallableLoader":{do_load:[29,3,1,""]},"gino.loader.ColumnLoader":{do_load:[29,3,1,""]},"gino.loader.Loader":{do_load:[29,3,1,""],get:[29,3,1,""],get_columns:[29,3,1,""],get_from:[29,3,1,""],query:[29,3,1,""]},"gino.loader.ModelLoader":{distinct:[29,3,1,""],do_load:[29,3,1,""],get_columns:[29,3,1,""],get_from:[29,3,1,""],load:[29,3,1,""],none_as_none:[29,3,1,""],on:[29,3,1,""]},"gino.loader.TupleLoader":{do_load:[29,3,1,""]},"gino.loader.ValueLoader":{do_load:[29,3,1,""]},"gino.schema":{AsyncSchemaDropper:[30,2,1,""],AsyncSchemaGenerator:[30,2,1,""],AsyncSchemaTypeMixin:[30,2,1,""],AsyncVisitor:[30,2,1,""],GinoSchemaVisitor:[30,2,1,""],patch_schema:[30,1,1,""]},"gino.schema.AsyncSchemaDropper":{visit_foreign_key_constraint:[30,3,1,""],visit_index:[30,3,1,""],visit_metadata:[30,3,1,""],visit_sequence:[30,3,1,""],visit_table:[30,3,1,""]},"gino.schema.AsyncSchemaGenerator":{visit_foreign_key_constraint:[30,3,1,""],visit_index:[30,3,1,""],visit_metadata:[30,3,1,""],visit_sequence:[30,3,1,""],visit_table:[30,3,1,""]},"gino.schema.AsyncSchemaTypeMixin":{create_async:[30,3,1,""],drop_async:[30,3,1,""]},"gino.schema.AsyncVisitor":{traverse_single:[30,3,1,""]},"gino.schema.GinoSchemaVisitor":{create:[30,3,1,""],create_all:[30,3,1,""],drop:[30,3,1,""],drop_all:[30,3,1,""]},"gino.strategies":{GinoStrategy:[31,2,1,""]},"gino.strategies.GinoStrategy":{create:[31,3,1,""],engine_cls:[31,4,1,""],name:[31,4,1,""]},"gino.transaction":{GinoTransaction:[32,2,1,""]},"gino.transaction.GinoTransaction":{commit:[32,3,1,""],connection:[32,3,1,""],raise_commit:[32,3,1,""],raise_rollback:[32,3,1,""],raw_transaction:[32,3,1,""],rollback:[32,3,1,""]},gino:{aiocontextvars:[18,0,0,"-"],api:[19,0,0,"-"],create_engine:[17,1,1,""],crud:[20,0,0,"-"],declarative:[21,0,0,"-"],dialects:[22,0,0,"-"],engine:[25,0,0,"-"],exceptions:[26,0,0,"-"],ext:[27,0,0,"-"],get_version:[17,1,1,""],json_support:[28,0,0,"-"],loader:[29,0,0,"-"],schema:[30,0,0,"-"],strategies:[31,0,0,"-"],transaction:[32,0,0,"-"]}},objnames:{"0":["py","module","Python \u6a21\u5757"],"1":["py","function","Python \u51fd\u6570"],"2":["py","class","Python \u7c7b"],"3":["py","method","Python \u65b9\u6cd5"],"4":["py","attribute","Python \u5c5e\u6027"],"5":["py","exception","Python \u4f8b\u5916"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:attribute","5":"py:exception"},terms:{"0020":39,"02":[10,15],"03":15,"04":[10,15],"05":15,"06":15,"07":15,"08":[10,15],"09":15,"0x10a8ba860":12,"10":[2,3,8,10,15,25,34,39,41],"100":[3,20,40],"106":37,"109":37,"11":[15,39,40],"112":37,"113":[8,37],"114":37,"11th":3,"12":[6,15,39,40],"126":37,"127":40,"128":32,"13":[37,39,40],"14":[15,39],"141":37,"142":37,"146":37,"147":37,"15":[15,39],"159":37,"16":[9,15,39,40],"160":37,"17":[9,37,39,41],"174":37,"178":37,"18":[9,15,39],"180":37,"183":37,"184":37,"187":37,"19":[15,39],"191":37,"192":37,"193":37,"198":37,"1990":9,"20":[8,10,15,39,40],"2017":[15,39],"2018":[10,15],"2019":15,"202":37,"2020":[14,15],"204":37,"21":[15,39],"213":37,"215":37,"216":37,"21s":40,"22":[37,39],"224":37,"225":37,"228":37,"23":[10,15,39,40],"231":37,"24":[15,39],"25":[15,39],"258":37,"26":[15,39],"261":37,"265":37,"27":[37,39],"275":37,"279":37,"28":[15,39],"280":37,"281":37,"282":37,"287":37,"288":37,"289":37,"29":[37,39],"291":37,"297":37,"298":37,"30":[37,39],"302":37,"304":37,"305":37,"307":37,"308":37,"309":37,"31":[37,39],"310":37,"313":37,"32":[20,32,37,39],"323":37,"32c0feba61ea":40,"32c0feba61ea_add_users_t":40,"33":[37,39],"333":37,"334":37,"335":37,"34":37,"351":37,"365":37,"378":37,"38":37,"382":37,"387":37,"39":37,"393":37,"395":37,"396":37,"3apull_request":6,"400":34,"401":37,"402":37,"403":37,"404":40,"406":37,"407":37,"408":37,"411":37,"42":20,"425":37,"427":37,"43":37,"431847":10,"433":37,"437":37,"440":37,"441":37,"447":37,"451":37,"457":37,"47":37,"486":37,"487":37,"4888":39,"4c59ad":37,"504":37,"518":37,"520":37,"53010":40,"53015":40,"533":37,"538":37,"54":40,"5432":[6,34,35,40],"5433":6,"567":37,"569":37,"573":37,"577":37,"579":37,"582":37,"585":37,"59":37,"592":37,"599":37,"60":37,"600":[6,37],"609":37,"627":39,"628":37,"629":37,"63":37,"63562":40,"63563":40,"637":37,"638":37,"64":32,"655":37,"660":37,"661":37,"662":37,"67":37,"672":37,"673":37,"674":37,"693":37,"694":37,"695":37,"696":37,"73":37,"75":37,"76":37,"79":37,"80":[37,40],"8000":40,"84":37,"87":[37,39],"89":37,"90":37,"abstract":[3,21,29,37],"boolean":[9,25,29],"break":[1,13,37],"case":[2,3,5,8,10,11,12,13,19,37],"class":[2,5,8,9,10,11,12,19,20,21,23,24,25,28,29,30,31,32,34,37,39,40,41],"default":[2,3,5,6,9,10,11,12,13,19,20,21,23,25,28,29,34,35,37,40,41],"do":[1,2,3,4,5,6,10,13,25,34,35],"enum":[23,37],"export":[5,6],"final":[2,3,8,25,37],"float":9,"for":[1,2,3,4,5,6,9,10,12,13,17,19,20,21,23,25,27,29,31,32,34,35,37,39,40,41],"function":[2,6,9,18,23,25,29],"if":[2,3,5,6,8,9,10,12,13,18,19,20,21,23,25,29,31,32,34,35,37,40],"import":[2,3,5,8,9,10,11,12,18,19,21,27,29,31,34,35,37,39,40,41],"in":[1,2,3,4,5,6,9,10,11,12,13,19,20,21,23,25,29,32,34,35,37,39,40],"int":[9,13,34,40],"long":[1,2,3,13,25,34,35],"new":[2,3,5,6,8,10,12,19,20,21,25,29,31,34,37,41],"null":[2,37],"public":12,"return":[2,3,8,9,10,12,13,19,20,21,23,25,29,31,34,35,37,40,41],"short":[2,3,12],"static":24,"super":[3,10],"switch":[2,19,37],"transient":2,"true":[2,3,5,8,9,10,12,13,19,20,21,23,24,25,29,30,34,35,39,40,41],"try":[2,3,8,13,25,32,37],"var":6,"while":[1,2,3,8,10,12,19,20,25,32,37],"with":[1,2,3,4,5,6,10,12,13,15,17,19,20,21,25,29,32,33,37,39,40,41],__:40,__all__:37,__attr_factory__:21,__init__:[10,40],__main__:34,__metadata__:21,__model__:37,__name__:[21,34,40],__repr__:34,__table__:[12,20,21],__table_args__:[21,37,41],__tablename__:[5,8,9,10,12,21,34,37,39,40,41],__values__:21,_base:23,_bind:8,_child:10,_children:10,_engin:23,_event:23,_idx1:41,_idx2:41,_is_metadata_oper:30,_name_idx:8,_parent:10,_pk:41,_schematranslatemap:25,_test:40,_update_request_cl:37,abandon:37,abcd:6,abil:[10,37],abl:3,abnormal_detect:9,abort:34,about:[1,2,3,5,8,12,13,20,25,37],abov:[2,3,8,10,29],absolut:[2,3],accept:[2,19,20,23,25,37],access:[0,2,8,12,13,21,25,32,34,37],access_log:9,accid:2,accord:[3,25,29,35],achiev:[8,10,12,19,25],acquir:[2,3,12,19,23,24,25,32,34,37],acquisit:3,across:29,act:2,action:[6,32],activ:[6,25,40],actual:[2,3,10,12,13,20,21,25,34],add:[2,3,5,6,8,10,19,20,27,37,40,41],add_child:10,add_us:40,added:[19,21,25,37,40],addit:[2,9,10,29],addition:[3,20],address:[5,12],adjac:8,admin:4,adopt:37,advanc:4,advic:3,affect:[20,37],after:[1,2,3,5,12,13,19,20,23,25,29,34,37,41],after_get:9,afterward:23,again:[2,3,8,10,25,41],against:6,age:[9,20,32],age_idx:9,aggreg:20,aintq:39,aiocontextvar:[2,4,15,16,17,37,39],aiohttp:[37,39],alemb:[4,9,12,19,38,39,41],alembic_sampl:5,ali:8,alia:[8,10,20,29,37],aliasload:29,alik:37,all:[1,2,3,5,6,8,9,10,12,13,19,20,21,24,25,29,32,34,35,37,41],all_us:41,allow:[2,12,19,21,23,37],alon:2,alpha:[8,37],alpin:[6,40],alreadi:[3,10,21],alright:1,also:[2,3,5,8,10,12,13,19,20,21,23,25,27,29,37],altern:[2,8,11,25],although:10,alwai:[2,5,6,8,12,19,20,25,29,32,34,37],amaz:8,amount:2,an:[1,2,3,6,8,10,11,12,13,19,20,21,23,25,27,29,32,34,37,39],and:[2,3,4,5,6,9,10,11,12,13,19,20,21,23,25,27,29,31,32,34,35,37],andrei:39,ani:[2,3,5,8,19,21,23,25,27,29,37],anoth:[2,3,10,20,25,29],answer:8,anyth:[3,12,25,37],api:[2,3,8,12,15,17,23,25,32,38,39,41],apirout:40,apk:40,app:[3,8,11,34,35,37,40],append:20,append_where_primary_kei:20,appli:[2,4,12,20,25,32,39,41],applic:[3,8,19,35,37,40],appreci:6,approach:[3,8],arbitrari:3,archlinux:39,are:[1,2,3,6,8,9,10,11,12,13,19,20,21,25,29,32,34,37,41],arg:[17,19,20,21,23,24,25,30,32],argument:[2,8,10,19,20,23,25,29,31,35,37],around:[25,39],arq:39,arrai:[9,23,37],arrayproperti:[9,28],arriv:1,arrrrh:1,as:[2,3,5,8,9,10,11,12,13,19,20,21,23,25,29,31,32,34,37,40,41],ascend:10,ascii_lett:[8,10],asgi:40,ask:2,assembl:[2,10],assert:[13,21,32,37,40,41],assertionerror:37,assign:2,assist:10,associ:[2,12],assum:[3,40],assumpt:32,async:[1,2,3,8,10,12,13,19,20,23,24,25,30,31,32,34,37,39,40,41],async_execut:[23,24],asyncdialectmixin:[23,24],asyncenum:23,asynchron:[0,2,10,12,13,19,25,32],asyncio:[0,2,4,10,12,14,18,39,41],asyncpg:[2,3,8,11,12,13,14,15,16,17,22,25,31,35,37,39,40,41],asyncpg_deleg:37,asyncpgcompil:23,asyncpgcursor:23,asyncpgdbapi:23,asyncpgdialect:23,asyncpgexecutioncontext:23,asyncpgiter:23,asyncpgjsonpathtyp:23,asyncpgsa:[12,39],asyncschemadropp:30,asyncschemagener:30,asyncschematypemixin:30,asyncvisitor:30,at:[2,3,6,8,10,12,13,19,20,25,34,35,37],ath:20,atom:20,attent:34,attribut:[8,10,12,20,21,25,29,37],attributeerror:19,audit_profil:9,aur:39,austin:8,authent:3,author:[3,19,39,40],author_id:39,auto:[9,20,21],autocommit:3,autogener:[5,40],autom:10,automat:[10,18,20,21,25,29,31,32,34],avail:[2,8,12,13,20,21,25,32,35,37],averchenkov:37,avoid:[3,29],awai:[2,3],await:[1,2,3,8,9,10,12,13,19,20,25,29,32,34,35,37,39,40,41],awesom:[1,39],back:[2,3,10,13,19,25,32,37],backend:40,backport:[2,8,37],backward:37,bad:37,balanc:[3,20],barancsuk:37,bare:3,base:[5,8,10,11,15,16,17,19,20,21,22,23,29,40],base_exp:28,basedbapi:[23,24],baseexcept:[13,32],basemodel:40,basic:[2,3,4,20,39],batch:[4,20],bayer:[3,39],be:[0,1,2,4,5,6,10,11,12,13,19,20,21,23,25,27,29,32,34,35,37],becaus:[2,3,8,12,13,19,20,32,37],becom:[2,12],been:[2,23,37],befor:[1,2,3,6,8,10,12,13,20,25,32,34,41],before_set:9,begin:[3,23,24],behav:[20,37],behavior:[2,8,13,20,25,34,37],behind:[2,3,10,20,37],being:[2,3,12,20],belong:13,below:[6,8,9],benefici:3,besid:8,best:[6,37],beta:37,better:[3,37,39],between:[2,3,37],beyond:3,biginteg:[19,34,40],bin:40,binari:[24,37],bind:[2,3,8,12,13,19,20,23,30,37],bind_processor:[2,23],bindtempl:23,binghan:37,birthdai:9,bit:[6,12,20,37],bite:[25,34],black:37,blob:[39,40],block:[2,3,13,25,32,34],blue:2,bondar:39,book:[8,19,39,41],booker:41,bookings_idx_booker_room:41,bookings_idx_day_room:41,bookings_pkei:41,bool:[9,40],booleanproperti:[9,28],borrow:[2,13,25,34,35],boss:1,bot:39,both:[2,3,8,10,12,19,20,21,23,32,37,41],bottleneck:3,bound:[19,20,21,34],branch:6,brien:37,broken:37,browser:6,bryanforb:39,bsd:14,buffer:3,bug:[6,8,37,39],bugfix:6,build:[3,6,8,10,19,20,40],builder:[10,40],built:[8,12,20,21,25,35,37],builtin:[24,34,37],bulk:[3,4,25],bunch:5,busi:[3,12],but:[1,2,3,8,10,12,13,19,20,21,25,29,37],by:[2,3,6,8,10,12,13,19,20,21,23,25,29,32,34,35,37,39],c10k:1,cach:40,call:[2,3,8,10,13,18,19,20,21,23,25,29,32,37],call_next:8,callabl:[10,23,25,29,37],callableload:29,caller:8,came:3,can:[1,2,3,4,5,6,10,11,12,13,19,20,21,25,29,32,34,35,37],candid:37,cannot:[2,10,37],canopi:39,canopytax:39,cap:3,cast:[9,40],categori:10,categories_1:10,categories_2:10,caught:32,caus:[2,3,13,25,34,35,37,41],cd:[6,40],cdi:39,celeri:3,certain:[3,34],chain:[2,3,19,20,29,37],challeng:3,chanc:3,chang:[5,6,8,20,25,37,40],chat:3,check:[2,6,8,10,12,20,23],checkfirst:[23,30],checkout:6,child:[10,37],child_id:10,children:[10,32],chmod:6,choic:[3,8,10],choos:[3,37],ci_:40,classic:10,classmethod:[20,24,29],claus:[2,8,10,12,19,20,23,24,25,29],clean:[8,37],cleanli:[3,25],cleanup:35,clear:3,click:2,client:[3,6,39,40],clone:6,close:[2,3,13,19,23,24,25,32,34,37,41],cls:[9,21],cmd:40,cn:[6,39],code:[2,3,8,10,12,13,25,32,37,41],coin:3,col:10,collect:[20,40],collid:37,color:[2,23,24,25],colspec:23,coltyp:23,column:[2,4,5,9,10,12,19,20,21,23,25,29,34,37,39,40,41],column_kei:23,column_nam:20,columnattribut:21,columnload:[8,10,29],com:[6,8,14,39,40],combin:[2,10],come:12,command:[3,5],command_timeout:23,comment:25,commit:[3,6,13,23,24,25,32,37,40],common:[12,13,25,35],commun:39,compani:29,compar:[3,20,40],compat:[2,8,25,29,37],compil:[19,24,25],complet:[2,8,19,37,40],complex:[4,10,39],compos:40,composit:20,concentr:3,concept:[2,12],conceptu:12,conclus:3,concret:[2,12,21,34],concurr:3,condit:[10,20],config:[8,11,34,35,37,40],configur:[6,15,29,33,34],confirm:37,conflict:[10,29],conftest:40,confus:2,congratul:5,conn1:2,conn2:2,conn:[2,3,12,19,23,24,25,32,37],connect:[0,3,4,10,11,12,13,15,19,23,25,30,32,33,34,37,40],connection_cl:25,connection_class:23,connectionless:2,conradi:37,consid:[3,8,37],consist:37,constraint:[21,30,37],construct:[3,8,12,19,21],consum:3,contain:[5,8,21],content:[15,16],context:[2,3,8,13,19,23,24,25,29,32,34,35,37,39,40],contextu:[8,10,34],contextualgino:8,contextvar:[2,8,15,18,39],continu:[13,32],contrast:3,contribut:[4,37],control:[4,23,25,34,37],conveni:[2,3,12,13,19,20,39],convers:[2,23],convert:29,cool:1,cooper:3,cooperative_multitask:1,copi:[8,25,37,40],core:[2,4,8,14,19,21,37,39,41],coroutin:[1,2,3,20,25,31],correctli:[3,12,13,19,20,37],correspond:[2,10,20,21],correspondingli:[2,13,32],could:[2,3,8,10,20],count:[2,8,10,41],count_1:41,cov:40,cover:37,coverag:37,cpu:1,cpython:39,creat:[0,3,4,6,8,10,12,13,19,20,21,23,25,29,30,31,32,34,35,37,39,40,41],create_al:[8,9,10,12,19,30,37,41],create_async:[23,30],create_engin:[2,3,8,11,12,17,19,25,29,31,37,41],create_ok:30,create_pool:[2,37],create_t:40,create_task:8,createdb:[40,41],creation:[2,8,19,37],credenti:5,credit:6,cross:34,crt:6,crucial:13,crud:[2,3,8,10,12,15,16,17,19,25,37,39],crudmodel:[19,20,37],csrf:40,ctrl:40,ctx:10,cur:40,curatedlist:39,current:[2,8,11,13,17,20,23,25,37],current_connect:[0,25,37],current_databas:8,current_us:[3,39],cursor:[2,23,24,25],cursor_cl:[23,24],custom:[9,10,20,21,25,37],customiz:[21,37],cut:3,cutil:37,cve:40,dahlia:39,dai:41,daisi:[9,21,39,41],damn:1,danger:34,darwin:40,data:[2,8,9,10,20],databas:[0,2,4,5,6,9,10,12,13,19,20,21,23,25,29,32,34,35,37,39,40],datastructur:40,date:41,datetim:[8,9,10,21,29],datetimeproperti:[9,28],db:[2,3,4,6,8,9,10,11,12,13,19,20,21,23,25,30,32,34,35,37,39,40,41],db_age:20,db_databas:[34,40],db_driver:40,db_dsn:40,db_echo:[8,34,37,40],db_host:[11,34,40],db_kwarg:[11,34],db_name:[5,6],db_pass:6,db_password:[34,40],db_pool_max_s:[34,40],db_pool_min_s:[34,40],db_port:[6,34,40],db_retry_interv:40,db_retry_limit:40,db_ssl:40,db_use_connection_for_request:[34,40],db_user:[6,34,40],dbapi:[23,24],dbapi_class:[23,24],dbapi_conn:23,dbapicursor:[23,24],dbname:[8,40],ddl:[30,40],dead:3,deadlock:3,deal:[3,8,10,13],debug:34,decim:37,declar:[10,12,15,16,17,19,20],declarative_bas:21,declared_attr:[9,21,37,41],decod:28,decor:21,def:[1,2,3,8,9,10,12,21,23,34,40,41],defaultdialect:23,defer:3,defin:[4,5,9,10,11,12,19,21,25,29],definit:[8,10,21],del:8,delai:3,deleg:[2,12,19,20,37],delet:[10,12,20,37,40,41],delete_us:40,demo:40,depend:[2,3,5,8,20,25,32,37,40],deploi:3,deprec:[29,37],describ:[3,8],descript:[5,6,23,24,35,40],design:[2,3,8],detail:[2,6,8,10],detect:40,determin:[3,10],deutel:37,dev:[39,40],develop:[5,6,15,34],diagram:2,dialect:[2,8,9,11,15,16,17,25,30,32,35,37],dict:[2,8,9,11,20,21,29,37,40],dictionari:[2,25,34],did:37,didn:[2,3,27],differ:[2,3,4,9,10,12,19,20,21,25,37],dig:12,direct:[3,25],directli:[2,3,8,10,12,19,20,21,25,34,37],directori:[5,37],dirti:[8,37],disabl:[20,37],disable_inherit:37,disable_task_loc:37,disadvantag:3,disallow:8,disast:[13,34],discard:[2,20,25],discord:39,discourag:3,discuss:8,disproportion:3,dist:37,distinct:[10,20,29,37],distinguish:2,divio:14,django:4,do_load:29,do_on_connect:23,doc:[5,6,8,13,37,39,40],docker:[6,40],dockerfil:40,docstr:6,document:[2,5,8,37,39],doe:[2,3,4,10,12,19,20,25,29,37,39],doesn:[2,3,8,9,10,21,37],doing:[3,19,37],don:[0,2,8,10,13,20,25],done:[0,1,2,5,6,8,12,13,37,40],doubt:10,down:[3,40],downgrad:[5,40],driven:6,driver:[2,8,13,23,25,35,37],drivernam:40,drop:[30,37],drop_al:[8,10,30],drop_async:[23,30],drop_ok:30,drop_tabl:40,dsn:[35,37,40],due:[3,37],dure:[2,21,25,34,37],dynam:[10,12,21],dziewulski:37,each:[2,3,5,8,10,21,25,29,34],earli:[13,15,32,35],earlier:[8,35],easi:[2,8,10],easier:8,easili:[2,3,10],echo:[2,8,25,35,37,40],ecosystem:3,edit:9,effect:[20,25],effici:2,effort:3,either:[2,3,5,8,12,13,20,25,32],elem:[19,24],element:19,els:[2,3,8,12,13,25,34,40],email:[3,8],email_address:12,emerg:37,empti:[2,34,35,37],en:[1,39],enabl:[2,6,8,20,21,29,35,37],enable_inherit:37,enable_task_loc:37,encapsul:[3,8],encod:[28,39],encourag:19,encrypt:6,end:[2,3,10,13],endpoint:3,enforc:[10,13],engin:[0,4,13,15,16,17,19,20,31,32,35,39,40,41],engine_cl:31,engine_from_config:19,enginestrategi:31,enhanc:[3,37],enjoi:34,enough:3,ensur:37,enter:[13,19,25],entri:[12,27,40],entry_point:40,env:[5,8,40],environ:[6,37,40],ep:40,equal:[2,20,37],equival:[20,21],error:[8,23,24,37],es:21,especi:[2,3,8,10,37],establish:34,etc:23,even:[2,3,8,12,19,20,21,25,34,37],event:[1,3,6,23,37,39],eventlet:39,eventu:[2,8],ever:32,everi:[6,10,20],everyon:2,everyth:[2,3,8,12,13,34],exactli:[2,10,19,25],exampl:[2,3,5,6,8,10,11,12,13,19,20,25,29,32,34,37,41],except:[1,2,8,13,15,16,17,23,24,25,32,34,35,37],exchangeratesapi:39,exec:6,execut:[0,3,4,9,10,12,19,20,23,24,25,29,35,37,41],executemani:[24,25,37],execution_ctx_cl:23,execution_opt:[2,8,10,19,20,25,29,37],executioncontextoverrid:[23,24],exhaust:[2,3],exist:[2,3,4,19,20,21,23,35,37],exit:[13,19,25,32],exp:9,expect:29,experiment:[10,20,29],explain:[3,8,10],explan:39,explicit:[0,2,8,10,13,37,39],explicitli:[2,3,8,9,32,34],explict:12,expos:[12,19,37],express:[4,9,20,25,29],ext:[8,11,15,16,17,19,34,35,37,40],extens:[2,8,11,12,19,27,34,35,37],extra:[29,37,40],extract:37,extrem:2,f2c273a43a3ed893a767d4239046f2befabf510d:39,face:3,facebook:39,fact:[8,23],factori:[21,35],fail:[2,19,37,40],fals:[2,3,9,12,20,23,24,25,30,34,35,37,40],fantix:[20,39,40,41],far:12,fast:[3,34,39],fastapi:[3,8,38,39],featur:[2,4,6,10,20,29,37],fed:[10,37],feed:[25,29],feedback:6,feel:[20,25],fetch:25,few:[2,3,8,25,34,37],field:[8,9,12,20],file:[1,5,6,37,40],fill:29,filter:20,find:[3,5,21,27],fine:[3,8,12],finish:[2,3,5,20,25,34,40],first:[2,3,4,6,8,10,12,19,20,23,24,25,29,34,35,37,41],first_connect:23,first_nam:8,first_or_404:37,firstli:10,fit:2,five:37,fix:[3,8,37],fixtur:40,flag:[23,35],flake8:6,flat:8,flexibl:[9,10,39],flow:3,flush:3,fly:4,focu:8,folder:5,follow:[2,3,8,9,13,19,29,32],forc:3,foreign:[8,10,20],foreignkei:[8,10,12,39],forev:[20,25],forget:2,fork:6,format:[8,34],former:2,fortun:3,forward:[10,23,24],found:[2,3,8,10,19,21,37,41],foundat:[3,39],founder:41,founding_us:41,fouser:19,framework:[8,37],free:[3,20,25],freebsd:39,freez:3,frequent:10,friendli:37,from:[2,3,5,8,9,10,11,12,13,19,20,21,25,29,32,34,35,37,39,40,41],fromclaus:29,full:[2,5,8],full_path:5,fulli:25,fullnam:12,fun:3,func:[8,10,29,41],fundament:3,further:[2,12,25,41],furthermor:[2,25,34],fut:8,futur:[8,10],gain:10,galden:37,garciasilva:39,gbasic:39,gcc:40,gener:[2,9,10,20,21,23,29,40],geoalchemi:39,get:[2,3,4,8,10,12,17,19,20,21,25,29,34,37,39,40,41],get_app:40,get_column:29,get_current_connect:37,get_event_loop:41,get_from:29,get_isolation_level:23,get_loc:37,get_now:2,get_or_404:[34,37,40],get_profil:28,get_raw_connect:25,get_result_proxi:24,get_statusmsg:[23,24],get_us:[34,40],get_vers:17,getattr:40,getlogg:40,getter:19,gevent:39,gino:[2,3,4,5,6,9,10,11,13,15,16,34,35,38],gino_db:6,gino_fastapi_demo:40,gino_fastapi_demo_test:40,gino_starlett:27,ginoconnect:[2,12,13,15,25,32],ginoengin:[2,8,12,13,19,20,25,29,31,32,37],ginoexcept:26,ginoexecutor:[2,19],ginonulltyp:23,ginopool:37,ginoschemavisitor:[12,19,30],ginostrategi:31,ginotransact:[13,25,32,37],git:[6,25,40],github:[6,8,14,39,40],gitignor:40,gitlab:39,gitter:14,give:[1,3,10,29],given:[2,6,8,10,19,20,21,23,25,29,31],global:[12,19],gmail:40,gnu:39,go:[1,5,12],goe:41,golden:3,goncharov:37,gone:37,good:[1,25],got:1,gotta:1,grai:2,grammar:[3,8,37],grandson:10,great:[3,5,8],greater:18,greatli:[2,6],green:2,greenlet:8,group_bi:[8,10],guarante:[3,13,25],guess:3,guess_model:37,guidelin:4,gunicorn:40,hack:8,had:[12,21],hand:2,handl:[2,3,13,25,32,37],handler:34,hang:3,happen:[3,19,25],hard:3,hardwar:3,harm:3,has:[2,3,8,10,12,13,19,20,23,25,37],has_schema:23,has_sequ:23,has_tabl:23,has_typ:23,haunt:3,have:[1,2,3,5,8,10,12,13,20,29,32,35,39],head:[2,5,40],hei:[1,3],height:9,help:[0,6,13],henc:19,here:[1,2,3,6,8,9,10,12,13,20,25,32,37,40,41],herebi:19,hidden:2,hide:[2,37],hierarch:[8,10],high:3,hit:3,hmm:2,hold:[1,8,13],holubakha:37,hong:39,hood:[2,20],hook:[4,8,19,23,27],host:[23,35,40],how:[0,2,4,5,6,10,13,25,39],howev:[2,3,8,12,19,20,25,37],html:39,http:[1,5,6,8,14,25,39,40],hurri:1,hybrid:3,id:[5,8,9,10,12,19,20,21,29,34,39,40,41],id_val:8,ide:39,idea:3,ident:[2,10,12,20,37],identifi:3,idl:3,ids:10,ila:37,im:14,immedi:[2,3,20,25,32],immut:2,impl:40,implement:[2,21,29],implic:12,implicit:[0,3,8,13,19,37,39],implicitli:[2,3,8,12,13,25,32],importantli:3,importlib:40,imposs:3,improv:37,in_:10,in_queri:20,inc:39,includ:[6,8,20,35,37],include_foreign_key_constraint:30,include_rout:40,increment:20,index:[4,10,21,30,39,41],index_on_nam:8,indic:[21,29],individu:[2,20],infer:20,info:40,inform:[2,8,12,13,19,25],inherit:[2,8,13,20,25,37],ini:[5,40],init:[5,23,37,40],init_app:[11,34,35,40],init_kwarg:23,init_pool:[23,24],initi:[2,4,9,12,20,21,23,29,31,35],inject:[2,12],inlin:[12,19,23,37],inner:[2,13,23],ins:12,insert:[4,12,13,20,25,37,39,41],insid:5,insist:10,inspir:[10,12],instal:[5,6,35,37,40,41],instanc:[2,3,5,8,9,10,12,13,19,20,21,23,25,28,29,31,37],instant:20,instanti:[12,20,25,29],instead:[2,3,10,12,13,20,21,25,27,37],integ:[5,8,9,10,12,21,39,41],integerproperti:[9,28],integr:[34,37],intend:21,intens:3,interfac:[2,4,19,29,37],interfaceerror:23,interfer:37,intern:[2,8,10,13,20,21,32,37],internet:3,interpret:[10,32],interv:23,into:[2,3,6,8,9,10,12,13,20,23,25,27,29,37,41],introduc:[8,10,37],intuit:3,invalid:34,invent:8,invert_get:21,invertdict:21,invok:[20,23],involv:[8,12],io:[1,25,39],irrelev:19,is:[1,2,3,4,5,6,9,10,12,13,14,18,19,20,21,23,25,29,31,32,34,35,36,37,39],is_:20,is_local_root:37,isdigit:34,ish:3,isol:[2,23,25,37],isolation_level:2,issu:[3,6,8,9,37,39],it:[1,2,3,4,5,6,10,12,13,19,20,21,23,25,29,31,34,37,39],item:[10,12,29,30,40],iter:[2,8,10,12,19,20,21,23,24,25,37,39],its:[2,3,8,10,19,20,21,23,25,29,32,35,37,41],itself:[2,3,8,10,12,13,21,23],iuliia:37,jack:12,java:39,jeff:8,jekel:37,jetbrain:39,jim:37,job:25,join:[4,10,19,20,29,37,39],join_queri:10,join_without_n_plus_1:3,jone:[12,39],json:[4,20,23,34,37,40],json_support:[15,16,17,20],jsonb:[9,37],jsonpathtyp:[23,37],jsonproperti:[9,20,28],julio:37,just:[1,2,3,5,8,10,12,13,20,21,32,34,37],keep:[3,8,10,37],kei:[6,8,10,20,21,29,37],kentoseth:37,kept:[3,29],keyout:6,keyword:[2,8,10,20,29],kill:[1,3],kind:3,king:[39,40],kinwar:37,know:[1,2,3,8,13],knowledg:[10,12,20],known:[2,10],ko:37,kooten:37,kovalev:37,kubernet:40,kw:[10,23,30],kwarg:[17,19,20,21,23,24,25,30,31,32,35,37],label:[29,37],lacerda:37,lambda:10,larg:[3,25],last:[2,3,10,19,20,32],last_nam:8,later:[1,2,8,10,12,25,35],latest:[20,25,39],latter:2,law:3,layer:3,layout:37,lazi:[0,8,15,25,33,34,37],lazili:[2,34],lazy_engin:8,lead:[3,9],leaf:10,learn:[2,3],least:3,leav:25,led:25,left:[2,10,20,39],legaci:12,len:10,length:12,lengthi:3,leosussan:39,less:[2,3,12],let:[1,2,3,10,12,13],level:[2,3,8,9,12,20,21,23,25],leverag:[3,9],li:37,lib:[6,40],libffi:40,librari:[37,39],licens:39,liebig:3,lifetim:34,lightweight:5,like:[2,3,5,6,8,9,10,12,20,21,25,27,34,35],likewis:[2,10,29],limit:[3,23,24,37],line:[2,5],link:[3,5,8],list:[2,6,8,9,12,25,29],listen:[3,23],liter:25,littl:6,ll:[1,2,3,8,9,10,25,37],load:[2,3,4,10,19,20,25,29,37,39,40],load_modul:40,loader:[4,8,15,16,17,19,20,24,25,37,39],lobbi:14,local:[2,5,6,15],localhost:[3,6,8,10,11,12,29,34,35,40,41],locat:[20,37],lock:[3,40],log:40,logger:40,logging_nam:[2,25],logic:3,login:6,longer:[2,3,8,25,37],look:[1,3,10,20,35],lookup:[20,37],loop:[8,19,23,24,25,31,37,39],lose:25,lost:3,lot:[3,10],love:3,low:3,lower:8,made:[8,10,12,37],magic:[2,10],magicstack:[8,39],mai:[2,3,8,9,10,13,19,25,34],main:[2,3,5,8,10,12,40,41],main_app:5,maintain:[3,8,21,39],mainten:37,major:3,make:[2,3,5,6,8,10,20,23,25,34,40],make_express:28,make_url:40,manag:[0,1,13,19,25,32,34,37],mandatori:12,mani:[2,3,4,8,23,24,25,37],manipul:37,manual:[4,8,9,10,12,20,25,32,37],map:[2,8,10,12,39],mapper:8,marissa:8,mark:[3,21,25],martin:37,masonri:40,massiv:20,master:[39,40],match:10,matter:[2,10,12],max:[2,40],max_cacheable_statement_s:23,max_cached_statement_lifetim:23,max_inactive_connection_lifetim:23,max_overflow:3,max_queri:23,max_siz:23,maximum:35,me:[1,2,3],mean:[2,3,10,13,20,21,23,25,37],meaningless:[2,21],meant:2,meanwhil:[20,32,37],meet:6,member:41,memori:[2,8,20,21,25],mention:[2,12],mess:19,messag:[3,37],meta_path:27,metaclass:20,metadata:[2,8,12,19,20,21,30,37,39,40,41],method:[2,3,12,19,20,21,23,25,29,32,37],micha:37,michael:39,middl:35,middlewar:[8,35,37],might:2,migrat:[4,15,40],mike:3,min:40,min_siz:[2,23],minhe:39,minim:12,minimum:3,misc:37,miser:3,miss:[2,37],mission:3,mistak:37,mix:3,mixin:[21,37,41],mkdir:40,mkvirtualenv:6,mock:29,mod:40,mode:[2,3,23,25,32,35],model:[2,3,4,5,8,9,12,19,20,21,24,25,29,34,37,38,39,41],model_base_class:19,model_class:[19,21],model_kei:8,modelload:[8,10,29,37],modern:3,modif:41,modifi:10,modul:[2,5,8,15,16,37,40],modulenotfounderror:5,moment:[1,2,3],more:[2,3,8,10,12,13,19,20,21,25,37],morgan:37,most:[2,3,8,12,13,20,25,37],mostli:[3,20],move:37,much:[2,3,9,12,13,25],multiparam:[2,19,24,25,37],multipl:[2,4,10,25,29,37],multipleresultsfound:[25,26],multitask:3,musl:40,must:[2,3,5,8,12,19,25,29],mutabl:37,my_app:5,myapp:40,mydb:40,mydb_test:40,mydialect:23,mykyta:37,mymodel:40,myself:[1,3],mysql:[8,39,41],mytab:13,mytabl:13,myuser:20,name:[2,3,5,6,8,9,10,12,19,20,21,29,31,34,35,37,39,40,41],name_or_url:31,namespac:27,nativ:[9,37],natur:3,neal:37,neat:3,necessari:[3,23,34],necessarili:2,need:[1,2,3,5,6,8,9,10,12,13,19,25,34,35,37],nest:[2,4,10,25,29,32],network:[2,3],never:[3,12,13,25],nevertheless:3,new_child:10,new_nam:8,new_names_dict:8,newli:[20,23],next:[3,5,12,23,24,25],nicknam:[5,12,34,40,41],no:[1,2,3,5,8,10,12,13,18,19,20,21,23,25,32,37,40],no_deleg:19,non:[8,11,12,25,37],nonam:[5,12,41],none:[2,8,10,12,19,20,21,23,24,25,28,29,30,31,35,37,40,41],none_as_non:[15,20,29],noresultfound:[25,26],normal:[2,3,9,10,12,13,19,20,21,32],nosuchrowerror:26,not:[1,2,3,4,10,12,13,14,19,20,21,23,25,29,32,34,37,39],note:[2,10,25,34,37],noth:[1,2,3,8,13,29,37],notic:[2,10],now:[1,2,3,5,6,8,10,12,13,19,25,29,37],nullabl:[9,12,40],nullpool:[11,23,37],nulltyp:23,number:[2,3,9,20,35],numer:24,obj:30,object:[2,3,5,8,9,10,12,19,20,21,23,24,25,28,29,30,32,37,39,40],objectproperti:[9,28],obviou:3,obvious:[3,37],occasion:[3,19],occur:23,of:[1,2,3,4,5,9,12,13,19,20,21,23,25,27,29,32,34,35,37,39],off:[2,3,34,37],offer:[2,12,32,37],offici:[5,19,27],often:[19,20,21],oh:1,okai:1,olaf:37,old:[20,39],olexii:37,omit:[10,20],on:[1,2,3,4,6,10,12,13,19,20,21,25,29,32,34,35,37,39,40],on_claus:[20,29],on_connect:23,onc:[2,3,5,8,10,23,25],one:[1,2,3,4,5,8,12,19,20,25,29,37],one_or_non:[2,12,19,25,37],ones:[2,3,19,29],onli:[2,3,5,10,11,12,13,19,20,21,25,29,32,35],ons:2,op:[18,40],open:[5,6,13,25,39],openid:3,opensourc:39,openssl:[6,40],oper:[2,3,19,20,21,25,37,39],opposit:[3,25],opt:25,optim:2,option:[2,8,10,11,19,20,23,25,29,37],or:[2,3,4,5,6,10,12,13,18,19,20,21,23,25,29,31,32,34,35,37,40],order:[2,8,10,20,25],ordinari:10,org:[1,5,39],origin:[6,12,20],orm:[0,2,4,10,14,39,41],orphan:2,orz:39,os:[1,3],oss:39,other:[2,3,4,5,6,11,12,13,19,20,21,25,29,35,37],otherwis:[21,23,25],our:[3,5],out:[1,2,3,6,12,21,25],outer:[2,10,13,20],outerjoin:[8,10,29,37,39],output:[10,12],over:3,overal:[3,10],overhead:3,overload:3,overrid:[19,20,21,37],own:[2,3,8,10,11,12],owner:6,packag:[5,15,16,35,37,39],pai:34,pair:[10,20],parallel:2,param:[19,24,25],paramet:[2,4,12,23,24,25,29,35,37,41],paramstyl:[2,24],parent:[2,8,10,12,19,32,37],parent_id:[8,10,37],parents_x_children:10,parentxchild:10,pars:[25,37],part:[2,3,8],partial:[2,8,37],particular:23,particularli:20,pascal:37,pass:[6,23,25,29,35,37,40],passfil:23,passin:6,passiv:37,passout:6,password:[5,6,23,35,40],patch:[8,18,37],patch_asyncio:18,patch_schema:30,pattern:[3,8,10],pavol:37,payload:2,pem:6,pend:20,peopl:[3,10],pep:[37,39],per:[23,25],perform:[3,8,34],perman:[2,25,35,37],peter:39,pgcompil:23,pgdialect:23,pgexecutioncontext:23,pgjone:39,philip:39,piec:10,pip:[5,6,35,37,40,41],place:[21,25,37],plai:[2,3,19,25],plain:[2,8,39],plain_old_java_object:39,platform:[3,40],pleas:[1,2,8,9,10,12,13,19,25,34,37],plu:19,pluggi:40,plugin:40,poetri:[5,37,40,41],point:[3,25,27,40],poli:21,pool:[2,3,11,23,24,25,34,35,37,40],pool_class:[11,23],pool_max_s:[35,40],pool_min_s:[35,40],poolev:23,pop_bind:[2,19,37,41],popo:39,popul:[20,41],port:[3,12,23,35,40],posit:[20,23,25,29],possibl:[2,3,5,10,12,23,29,34,37],post:[2,10,40],postgi:39,postgr:[5,6,8,31,34,35,40],postgreserror:23,postgresql:[2,3,6,8,9,10,11,12,19,23,29,31,32,37,39,40,41],postgresqlimpl:40,postprocess:25,potenti:10,pr:[39,40],practic:[3,25],prefer:[3,29,34],prefetch:37,prefix:31,prepar:[5,23,24,25,37],preparedstat:[23,24],present:[2,10,25,37],press:40,pretti:[3,12],prevent:37,previou:[2,10,12,19,35,37],previous:[2,10],primari:[20,37],primary_kei:[5,8,9,10,12,19,21,34,39,40,41],primarykeyconstraint:[40,41],princip:3,print:[4,9,10,12,20,39,41],prioriti:3,privkei:6,probabl:2,problem:[3,5,37],proce:23,process:[2,10,23,37,40],process_row:24,processor:[10,37],produc:[2,10],product:0,profil:[9,28,37],program:[2,3,10,13],progress:2,prohibit:13,project:[5,6,8,40],promis:10,prop_nam:[9,21,28],propag:13,proper:3,properti:[2,3,4,5,8,10,12,19,23,24,25,29,32,37],protect:37,provid:[2,8,9,10,12,13,19,20,25,27,29,35,37],proxi:20,psql:6,psycopg2:[2,40],psycopg:40,publicli:[19,25],pull:4,pure:[8,37],push:6,put:[2,6,12,32],pwd:6,py:[5,6,8,37,39,40],pycharm:39,pydant:40,pypi:[14,37],pyproject:40,pytest:[6,40],python:[1,2,3,5,6,8,9,14,18,21,27,37,38,40,41],pythongino:39,pythonpath:[5,40],qbasic:39,qualiti:3,quart:[37,39],queri:[0,3,4,6,9,10,12,15,19,20,21,23,24,25,29,35,39,41],query_executor:19,query_ext:19,query_param:8,querymodel:20,question:3,queue:3,quick:4,quickli:8,quit:[2,3,8,12,20,40],qulaz:37,rais:[2,8,13,19,25,32,37],raise_commit:[13,32],raise_for_statu:40,raise_rollback:[13,32],raiseerr:40,ram:39,randint:[8,10],random:[8,10],rang:[8,10,37],rather:[3,8,20],raw:[2,3,4,10,11,12,25,29,37],raw_conn:[23,24],raw_connect:[25,37],raw_pool:[23,24,25,37],raw_transact:[13,23,24,32],rdbm:[34,41],re:[2,3,6,8,10,20,25,37],reach:[32,34],reaction:3,read:[2,8,19,20,21,25],read_root:3,readi:6,readm:[6,37],realiti:3,realli:1,reason:[3,23],receiv:[8,23],recent:[2,25],recogn:[13,20,37],recommend:[2,19,25,34],reconsid:3,record:37,recov:37,recurs:[10,25],recv:1,redirect:21,reduc:[10,12],refactor:37,refer:[2,8,10,13,19,25],referenc:[4,25,32],reflect:21,refresh:37,regular:[10,12],reinvent:3,rel:5,relat:[2,8,10,39],relationship:[3,4,20,25,37],releas:[2,8,15,23,24,25,34,35],relev:[19,25,37],reli:3,reliabl:3,reload:[20,28,40],remain:[8,37],rememb:[3,6,20],remind:3,remov:[2,20,37],renam:37,replac:[2,21,29,35,37],repli:3,repo:6,report:6,repr:[23,24,25],repres:[8,19,25,32],represent:37,req:6,requeijo:37,request:[3,4,8,20,34,35,40],requir:[2,3,6,12,19,20,21,25,37,40],requirements_dev:6,reset:37,reset_loc:37,reskov:37,resourc:[2,3],respond:3,respons:[10,25,34,35],rest:[9,20,32,34],restrict:19,result:[2,4,10,19,21,23,25,29,37],result_processor:[2,23],resum:[3,19],retriev:[2,10,20,40],retry_interv:40,retry_limit:40,return_model:[2,19,20,24,25],reus:[0,3,10,13,25,29,34,37],reusabl:[0,25],revamp:37,revers:[2,12,25],revert:[2,37],review:37,revis:[4,9,40],rewritten:[12,37],rewrot:37,rez:39,ricardo:39,rich:[10,37],right:0,risk:[2,12],rm:6,ro:6,roald:37,role:6,roll:[13,25,32],rollback:[3,13,23,24,25,32,37],roman:37,room:41,root:[3,25],rootdir:40,rout:34,router:40,row:[2,8,10,12,20,23,24,25,29,37],rowproxi:[2,12,29,37],rsa:6,rst:[6,40],rule:[2,12,13,25,29],run:[1,2,3,4,5,6,9,10,25,29,34,35,40],run_until_complet:41,runtim:40,rv:40,sa:40,sa_conn:25,sacrific:3,safe:[12,25],sai:[3,34],said:3,same:[2,3,4,5,9,10,12,13,19,20,21,25,29,34,35,37],sampl:5,sanic:[11,15,33,37,39],sanicframework:39,save:[2,25,28],savepoint:[13,32],scalabl:3,scalar:[2,3,8,12,19,20,24,25,37,41],scenario:[2,3,10,12],scene:2,schema:[5,12,15,16,17,19,21,23,25,39],schema_ext:19,schema_for_object:25,schema_visitor:19,schemadropp:30,schemagener:30,schemaitem:[19,37],scope:[3,34],sebasti:39,sec:1,second:[2,3,10,20,25],secret:40,section:6,secur:8,see:[1,2,5,6,8,12,13,25],seek:3,seem:[1,12],select:[2,3,8,9,10,12,13,19,20,25,29,37,39,41],select_from:[8,10,29],self:[4,8,9,19,20,23,25,29,34],send:[3,6,23],sens:3,sent:19,sep:10,separ:[3,6,10,13,37],sequenc:[2,23,30],sequence_nam:23,sergei:37,seri:[2,25],serv:3,server:[2,3,6,8,25,34,35,37,40],server_default:[8,9,10],server_set:23,servic:39,session:[3,40],sessionmak:3,set:[2,4,6,8,10,12,19,20,21,23,25,29,34,35,37,41],set_bind:[2,8,19,25,37,41],set_except:8,set_isolation_level:23,set_main_opt:40,set_result:8,setattr:[10,29,37],setter:[8,10,19],settl:34,setup:[5,23,35],sever:[2,19,20,25],shall:[13,20,34],share:[2,25,34,35,37],shortcut:[2,3,10,13,17,19,20,25,29],shortest:3,should:[2,3,6,10,11,19,20,21,23,25,27,35,37],shouldn:[3,37],show:10,shown:[2,32],shtrikker:37,shut:40,shutdown:40,side:[2,3,10,25],sign:1,silenc:1,simeon:37,similar:[2,5,6,10,12,13,19,20,37],similarli:[2,10,12,13,20,25,37],simpl:[2,3,5,8,12,19,37,39],simpler:2,simplest:29,simpli:[2,3,8,9,10,19,20,25,29,34,35],simul:[2,8],sinc:[1,37],singl:[2,3,10,12,20,23,25],situat:[3,25,32],skip:[13,32],sleep:[1,2,3],slower:3,small:2,smaller:3,smarter:3,so:[2,3,6,8,10,12,13,19,20,21,23,25,27,32,37],softwar:39,sole:23,solut:[3,27],solv:3,some:[2,3,5,6,8,9,10,12,20,25,35,37],someth:[2,3,10],sometim:[2,3,34],soon:34,sourc:[20,25,37,39],speak:2,special:[10,23,34],specif:[2,20],specifi:[2,9,10,12,13,20,21,23,25,29,35,37],sql:[2,3,4,9,10,12,19,20,23,25,30,37,39,41],sqlalchemi:[2,3,4,5,9,10,12,14,17,19,20,21,23,25,29,30,31,35,37,39,40,41],sqltype:23,src:[37,39,40],ssl:[4,6,23,35,37,40],ssl_cert_fil:6,ssl_key_fil:6,stabil:8,stabl:[37,39],stack:[2,25,37],standard:[2,3],starleet:37,starlett:[8,15,27,33,37,39,40],start:[2,3,4,8,13,20,25,32,34,37,40],startup:40,starv:0,starvat:3,state:3,stateless:[3,8],statement:[2,3,8,12,13,20,23,24,37],statement_cache_s:23,statement_compil:23,statu:[2,12,13,19,20,24,25,37,41],status_cod:40,stave:3,step:[2,3],stereotyp:3,still:[2,3,8,10,12,21,25,29,32,37],stop:[3,13,34],storag:[21,37],store:[1,2,9,10,21],storm:37,str:[3,9,29,40],straightforward:3,strategi:[2,15,16,17,29],string:[2,8,9,10,12,19,20,21,23,25,34,37,39,41],stringproperti:[9,28],strongli:3,structur:[5,10,20,25],stub:39,style:[3,10,12,37],sub:[3,8,21,29,37],subclass:[2,12,19,21,29,32,37],subj:6,subload:10,submit:6,submodul:[15,16],subpackag:[15,16],subqueri:[20,37],subset:6,succeed:25,success:5,successfulli:32,such:[2,3,8,20,23,25],sugar:3,suit:[20,37],summari:3,support:[2,4,6,9,10,15,18,19,20,25,29,33,37,39,40],supports_native_decim:23,suppos:[2,21,29,32],sure:[3,6,10,25,34],svx:6,swagger:40,sweet:1,symbol:19,sync:8,synchron:19,syntax:6,sys:27,system:[2,8,10,40],tabl:[4,5,10,12,13,19,20,21,23,30,37,40],table_nam:23,tableclaus:12,take:[2,3,8,11,12,20,25,29],taken:[2,25,34],talk:3,target_metadata:[5,8,40],task:[2,3,15,25,35],tast:12,team:[20,37],team_id:20,technic:20,telegram:39,tell:2,ten:3,termin:6,test:[6,37,40],test_aiohttp:6,test_crud:40,test_gino:6,test_us:40,testclient:40,text:[6,8,9,10,12,19,29,37],textual:[2,10],than:[2,3,8,10,11,13,20,21,25,39],thank:[2,37],that:[1,2,3,5,6,8,10,12,13,19,20,21,23,25,27,29,34,35,37],the:[1,2,3,4,5,6,9,10,11,12,13,19,20,21,23,25,27,29,32,34,35,37,39,40,41],thei:[2,3,6,8,10,12,13,20,21,25,37,41],their:[2,3,8,10,11,12,19,20,21],them:[3,5,8,9,10,20,37],then:[2,3,6,8,10,12,20,23,25,29,35,37],theoret:[3,37],there:[1,2,3,4,10,11,12,13,20,25,32,37],therefor:[2,3,8,12,19,20,25,34],these:[2,3,5,6,19,20,21,25,37],thi:[2,3,5,6,8,9,10,12,13,18,19,20,21,23,25,27,29,31,32,34,35,36,37,41],thing:[2,3,5,12,21,37],think:[3,8],third:[10,12],those:[3,25],though:[3,12,20,34],thousand:3,thread:[1,2,3],three:2,through:[2,4,6,10,13,19,27,37],throughput:3,thu:[2,3,12,13,25,37],tiago:37,tiangolo:39,tim:39,time:[1,2,3,5,8,9,10,13,19,20,25,34],timeout:[19,20,23,24,25,37],timeouterror:25,timestamp:9,timezon:21,tini:3,tip:4,titl:[39,40],to:[0,1,2,4,5,6,9,11,12,13,15,18,19,20,21,23,25,27,29,32,34,35,39,40,41],to_dict:[20,37,40],togeth:[2,25,32],token:3,toml:40,toni:[37,39],too:[2,3,8,12,13,25],tool:[3,5,39,40],toolkit:5,top:[2,3,8,12,25],tornado:[15,33,37,39],tortois:39,total:3,touch:[13,32],touchabl:2,tox:6,trackedmixin:21,tradit:[8,12],transact:[2,3,4,8,10,15,16,17,19,23,24,25,34,39,40],transform:10,translat:[2,9,10],trap:[13,32],travers:2,traverse_singl:30,treat:[2,10,21,25,31],tree:10,tri:[3,25,37],trigger:[3,21],trio:8,ts:29,tupl:[2,8,10,20,25,29],tupleload:[8,10,29,37],turn:[1,2,10,25,34,37],tutori:[12,39,40],twice:[2,4,23],twist:[3,39],two:[2,3,8,10,12,13,20,25,32],tx1:[13,32],tx2:[13,32],tx3:32,tx:[13,23,25,32,37],txt:6,type:[2,4,8,9,10,12,20,21,25,29,32,37],type_nam:23,typic:[3,8],ua1:10,ua2:10,ubuntu:39,uc:39,ui:40,uid:[10,40],ultim:3,ultra:39,um:1,unbind:19,under:[2,8,10,12,19,20,21,29,37,40],underli:[2,12,13,25,32,37],unfortun:3,unicod:[5,8,10,12,21,23,34,40,41],unifi:[2,37],uninitializederror:[8,26,37],uniqu:[2,10,41],unique_constraint:21,unique_id:21,uniqueconstraint:21,unix:39,unknown:[10,37],unknownjsonpropertyerror:26,unless:[2,3,20],unnam:[37,40],unnecessarili:34,unpin:37,unpredict:[3,34],unrecogn:35,unreli:3,unreus:2,unset:2,unspecifi:20,untest:2,until:[8,9,25],unus:[2,3],unwant:12,unwrap:23,up:[1,2,4,6,8,10,19,23,25,32,35],updat:[2,4,6,9,20,21,25,29,32,37,39,40,41],update_execution_opt:[25,37],updaterequest:[20,41],upgrad:[2,5,8,37,40],upon:[13,23,32],upstream:8,urh:1,url:[2,5,19,23,24,31,35,37,40,41],us:3,usabl:[2,25,37],usag:[2,4,5,20,25,35,37],use:[2,3,4,5,9,10,11,12,13,19,20,23,25,27,29,32,34,35,37,39,40],use_connection_for_request:[35,40],used:[2,8,10,11,12,19,20,21,23,25,29,37],useful:[2,3,12,20,25,29,32],user1:20,user2:20,user:[2,3,4,5,6,9,10,11,12,19,20,21,23,25,29,32,34,35,39,40,41],user_id:[8,10,12,20,34],usermodel:40,usernam:[5,40],users_t:2,uses:[8,10,20,31],using:[2,3,6,8,9,10,12,13,20,21,23,25,29,34,37],usual:[2,3,8,9,10,12,19,20,21,25],utc:10,util:[21,27],uuid4:40,uuid:40,uvicorn:[39,40],uvicornwork:40,uvloop:[1,39],v7oze:25,val:[8,9,28],valid:12,valu:[2,8,10,12,13,19,20,21,23,25,28,29,34,37,41],valueload:29,van:37,vanilla:[2,12],vargovcik:37,variabl:[5,39],variant:2,ve:[1,3,10],venv:40,veri:[1,2,3,9,10,12,20,23,25,32,34],version:[5,6,8,17,18,35,37,40],view:40,virtual:6,virtualenv:40,virtualenvwrapp:6,visit:[8,10,19],visit_foreign_key_constraint:30,visit_index:30,visit_metadata:30,visit_sequ:30,visit_t:30,visual:39,vladimir:37,volkova:37,volunt:6,wai:[2,3,6,8,9,10,12,13,19,25],wait:[3,9,13,20,25,40],wang:[37,39],wanna:[1,3],want:[2,3,5,6,12,19,20,25,34],ware:8,warn:[37,40],was:[2,3,10,13,25,32],wast:[2,3],watch:[1,39],we:[2,3,5,8,9,10,12,13,19,34,37],weakref:37,web:[8,37,39],websit:6,welcom:[6,12,37,39],well:[1,2,3,21],were:3,what:[1,2,3,4,12,13,19],whatev:[2,3,10,20,25],wheel:3,when:[0,1,2,5,6,8,10,12,13,18,19,20,21,25,29,32,34,37],where:[2,3,8,9,10,12,19,20,25,29,32,41],whether:[25,32,37],which:[2,3,8,10,12,19,20,21,23,25,32,34,37],whichev:37,whole:37,whose:[12,32],why:[3,10],wide:23,wiki:[1,39],wikipedia:[1,39],will:[2,3,4,5,6,9,10,13,19,20,21,23,25,27,29,31,32,35,37,40,41],wip:[34,36],wise:3,wish:[2,6,10,25],with_bind:[2,8,10,12,19,37],within:[3,13,23,25,34,37],without:[2,3,9,12,13,19,25,37],won:[1,2,3,8,12,13,32,37],wonder:1,work:[2,3,4,5,6,10,12,15,20,21,25,33,37],workaround:8,workdir:40,worker:40,world:[12,34],worri:[2,3,8,13,37],worth:[2,12],would:[3,8,10,19,20,29],wrap:[8,21],wrapper:[2,3,8,21,25,39],write:[2,3,8,10],written:[12,39],wrong:[3,37],wrote:3,ww4ronfhiqi:39,www:39,x509:6,xss:40,xxx:[20,39],xxxx:4,yai:[1,8],yes:2,yet:[1,8,9,25],yield:[3,12,20,21,25,29,40],yml:40,you:[1,2,3,5,6,8,9,10,12,13,19,20,21,25,32,34,35,37],your:[1,2,3,5,6,8,10,12,13,19,25,37,40],your_name_her:6,yourdbnam:40,youtub:39,yurii:37,za:37,zen:39,zenof:39,zero:[20,25],zh:39,zone:[9,10]},titles:["\u539f\u7406\u8bf4\u660e","\u5f02\u6b65\u7f16\u7a0b\u57fa\u7840","\u5f15\u64ce\u4e0e\u8fde\u63a5","\u4e3a\u4ec0\u4e48\u8981\u7528\u5f02\u6b65 ORM\uff1f","\u8fdb\u9636\u7528\u6cd5","\u4f7f\u7528 Alembic","\u8d21\u732e","\u589e\u5220\u6539\u67e5","\u5e38\u89c1\u95ee\u9898","JSON \u6269\u5c55\u5c5e\u6027","\u52a0\u8f7d\u5668\u4e0e\u5173\u7cfb","\u8fde\u63a5\u6c60","\u8868\u7ed3\u6784\u5b9a\u4e49","\u6570\u636e\u5e93\u4e8b\u52a1","\u6b22\u8fce\u6765\u5230 GINO \u7684\u6587\u6863\uff01","\u53c2\u8003\u624b\u518c","API \u53c2\u8003","gino package","gino.aiocontextvars module","gino.api module","gino.crud module","gino.declarative module","gino.dialects package","gino.dialects.asyncpg module","gino.dialects.base module","gino.engine module","gino.exceptions module","gino.ext package","gino.json_support module","gino.loader module","gino.schema module","gino.strategies module","gino.transaction module","\u6269\u5c55","Sanic Support","Starlette Support","Tornado Support","\u7248\u672c\u5386\u53f2","\u4e0a\u624b\u6559\u7a0b","\u5b98\u5ba3\uff1aPython \u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668","\u642d\u5efa\u4e00\u4e2a FastAPI \u670d\u52a1\u5668","GINO \u57fa\u7840\u6559\u7a0b"],titleterms:{"02":37,"03":37,"04":37,"05":37,"06":37,"07":37,"08":37,"09":37,"10":37,"11":37,"12":37,"14":37,"15":37,"16":37,"18":37,"19":37,"20":37,"2017":37,"2018":37,"2019":37,"2020":37,"21":37,"23":37,"24":37,"25":37,"26":37,"28":37,"do":8,"for":8,"in":8,"with":[8,34,35],access:3,admin:8,advanc:10,aiocontextvar:[8,18],alemb:[5,8,40],and:8,api:[16,19,37,40],appli:5,asynchron:3,asyncio:[3,8],asyncpg:23,base:24,basic:13,batch:8,be:[3,8],bulk:8,can:8,column:8,complex:8,configur:35,connect:[2,8,35],content:[17,22,27],contextvar:37,contribut:6,control:13,core:12,creat:[2,5,9],crud:20,current_connect:2,databas:[3,8],db:5,declar:21,defin:8,develop:37,dialect:[22,23,24],differ:8,django:8,doe:8,don:3,done:3,earli:37,engin:[2,8,12,25,37],except:26,execut:[2,8],exist:8,explicit:3,express:10,ext:27,fastapi:40,featur:8,first:5,fly:8,get:6,gino:[8,12,14,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,37,39,40,41],ginoconnect:37,guidelin:6,help:3,hook:9,how:[3,8],implicit:2,index:[8,9],initi:8,insert:8,interfac:8,is:8,it:8,join:8,json:9,json_support:28,lazi:[2,35],load:8,loader:[10,29],local:37,manag:2,mani:10,manual:13,migrat:[5,37],model:[10,40],modul:[17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],multipl:8,nest:13,none_as_non:37,not:8,of:[6,8,10],on:[5,8,9],one:10,or:8,orm:[3,8,12],other:10,packag:[17,22,27],paramet:8,print:8,product:3,properti:9,pull:6,python:39,queri:[2,8,37],quick:9,raw:8,referenc:10,relationship:[8,10],releas:37,request:6,result:8,reus:2,reusabl:2,revis:5,right:3,run:8,same:8,sanic:34,schema:30,self:10,set:5,sql:8,sqlalchemi:8,ssl:8,starlett:35,start:[6,9],starv:3,strategi:31,submodul:[17,22],subpackag:17,support:[8,34,35,36],tabl:8,task:37,the:8,there:8,through:8,tip:6,to:[3,8,10,37],tornado:36,transact:[13,32,37],twice:8,type:6,up:5,updat:8,usag:[10,13],use:8,user:8,what:8,when:3,will:8,work:[8,34,35],xxxx:8}}) \ No newline at end of file diff --git a/docs/zh/1.0/tutorials.html b/docs/zh/1.0/tutorials.html new file mode 100644 index 0000000..d5bccb7 --- /dev/null +++ b/docs/zh/1.0/tutorials.html @@ -0,0 +1,232 @@ + + + + + + + + 上手教程 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/tutorials/announcement.html b/docs/zh/1.0/tutorials/announcement.html new file mode 100644 index 0000000..754bb39 --- /dev/null +++ b/docs/zh/1.0/tutorials/announcement.html @@ -0,0 +1,488 @@ + + + + + + + + 官宣:Python 异步编程再添一利器 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    官宣:Python 异步编程再添一利器

    +
    +

    GINO 填补了国内外 asyncio ORM 领域的空白

    +
    +

    随着 Tornado1 和 asyncio2 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +GINO 了解一下!

    +../_images/python-gino.webp +
    +

    GINO 是谁

    +

    GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义3手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,你肯定要问一句“这是谁”。

    +

    ORM,即关系对象映射(Object-Relational Mapping4),是一类开发人员喜闻乐见的效率工具,它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢?

    +

    因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 current_user.name +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试?

    +

    传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,但应用到大型项目中却十分考验开发人员的平均水平。

    +

    所有这些问题如果再放进异步编程的环境里,那就是 O(n2) 的复杂度了——哦不,是 +O(2n)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。

    +

    所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,我索性在 1.0 稳定版发布的前夕做个年终总结。

    +
    +
    +

    先说“方便快捷”

    +
    +

    “方便快捷”主要说的是开发效率。

    +
    +

    重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。

    +
    from gino import Gino
    +db = Gino()
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +
    +

    这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟?

    +

    没有错,这就是 SQLAlchemy ORM5 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core6(SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic7、各种 SQLAlchemy 的增强插件8、专业领域的 PostGIS/geoalchemy9等,GINO 全都兼容。

    +

    是不是十分方便、十分快捷?不止这样。

    +

    GINO 一站式地解决了常用 CRUD 快捷方式10、上下文管理(aiocontextvars1112)、 +数据库事务封装和嵌套13、连接池管理和懒加载14等多项便捷功能,无额外依赖关系,即装即用。

    +
    daisy = await User.create(name="daisy")
    +await daisy.update(name="Daisy").apply()
    +
    +
    +

    GINO 还提供了15各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado1、aiohttp16、Sanic17、FastAPI18/Starlette19、Quart20什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。

    +

    为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa21 的降维打击:

    +
      +
    1. 最少侵入型22:SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。

    2. +
    3. 终身不婚型23:天生厌恶“对象”,只愿定义“表”,空手接 SQL。

    4. +
    5. 火力全开型24:最大程度的便利,非典型异步 ORM。

    6. +
    +

    最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、一秒可读百万行的 asyncpg25,以及 uvloop26(可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,深受俄罗斯和乌克兰人民的爱戴。

    +
    +
    +

    再说“简单明了”

    +
    +

    Explicit is better than implicit.

    +

    Simple is better than complex.

    +

    –The Zen of Python, PEP 2027

    +
    +

    Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,因此 GINO 的很多设计都受到了明确性的影响。

    +

    比如说,GINO 的 Model 是完全无状态的普通 Python 对象(POPO28—— 例如前面的 User +类,它的实例 daisy 就是内存里面的常规对象,你可以用 daisy.name 访问属性,也可以用 +daisy.name = "DAISY" 来修改属性,或者用 u = User() 来创建新的实例,这些操作都不会访问数据库,绝对绿色环保无毒副作用。

    +

    等到需要操作数据库的时候,你一定会有感知的。比如执行 INSERT 要用 +u = await User.create(),而 UPDATE 则是 +await u.update(name="Daisy").apply()

    +
    +

    提示

    +

    其中,u.update(name="Daisy") 与 u.name = "Daisy" 类似,都是只在内存里修改对象的属性,不同的是 u.update() 还会返回一个包含本次变更的中间结果,对其执行 await xxx.apply() 则会将这些变更应用到数据库里。

    +
    +

    这里的 await 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 await +就没有数据库操作。

    +

    另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders29。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 u = await User.get(1) 可以直接获取到 ID 为 1 的用户对象。但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。加载器的用法也是很简单的,比如一个用户可能写了很多本书:

    +
    class Book(db.Model):
    +    __tablename__ = "books"
    +    id = db.Column(db.Integer, primary_key=True)
    +    title = db.Column(db.String)
    +    author_id = db.ForeignKey("users.id")
    +
    +
    +

    然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者:

    +
    query = Book.outerjoin(User).select()
    +loader = Book.load(author=User)
    +async for book in query.gino.load(loader).iterate():
    +    print(book.title, "written by", book.author.name)
    +
    +
    +

    很简单的一个外连接查询 Book.outerjoin(User),配合一个直观的加载器 +Book.load(author=User),就实现了:

    +
      +
    1. 执行 SELECT * FROM books LEFT JOIN users ON ...

    2. +
    3. 将数据库返回结果的每一行中,属于 books 的字段加载成一个 Book 实例;

    4. +
    5. 然后将该行中剩下的属于 users 的字段加载成一个 User 实例;

    6. +
    7. 最后将 User 实例设置到 Book 实例的 author 属性上。

    8. +
    +

    既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。

    +
    +
    +

    优势与不足

    +

    随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM30、ORM31(是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。

    +

    GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,可以谨慎地用于生产环境了。

    +

    以下是近来统计到的关于 GINO 的应用案例:

    + +

    另外,GINO 还贴心地提供了中文文档32,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!):

    +../_images/docs.webp +

    GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。

    +
    +
    +

    建设社会主义

    +

    GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 4888 元的 PyCharm +专业版全家桶 License 一枚33(没有发票)。

    +

    目前急需帮助的有:

    +
      +
    1. 各个 Web 框架插件的维护工作需要多人认领;

    2. +
    3. 更多的例子和文档,以及中文、俄文的翻译;

    4. +
    5. MySQL 的支持。

    6. +
    +

    以及下面这些一直需要的帮助:

    +
      +
    1. 用 GINO,找 bug,提建议;

    2. +
    3. 修 bug,做功能,提 PR;

    4. +
    5. 维护社区,回答问题,参与讨论;

    6. +
    7. 最后也是最重要的:去 GitHub 上给 GINO 加一颗星星!

    8. +
    +../_images/happy-hacking.png +
    +
    +

    关于作者

    +

    87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P

    +

    特别感谢 Tony Wang 对 GINO 项目的贡献,以及所有人的贡献。

    +
    +
    +

    参考文献

    +
    +
    1(1,2)
    +

    Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado.

    +
    +
    2
    +

    Python Software Foundation. asyncio — 异步 I/O. +https://docs.python.org/zh-cn/3/library/asyncio.html.

    +
    +
    3
    +

    维基百科. GNU. https://zh.wikipedia.org/wiki/GNU.

    +
    +
    4
    +

    维基百科. 对象关系映射. +https://zh.wikipedia.org/wiki/对象关系映射.

    +
    +
    5
    +

    维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy.

    +
    +
    6
    +

    Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. +https://docs.sqlalchemy.org/en/13/core/index.html.

    +
    +
    7
    +

    Michael Bayer. Welcome to Alembic’s documentation!. +https://alembic.sqlalchemy.org/en/latest/.

    +
    +
    8
    +

    Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. +https://github.com/dahlia/awesome-sqlalchemy.

    +
    +
    9
    +

    Ricardo GarciaSilva. Does it have postgis support?. +https://github.com/python-gino/gino/issues/627.

    +
    +
    10
    +

    Fantix King. GINO 基础教程 - 增删改查. +https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations.

    +
    +
    11
    +

    Python Software Foundation. contextvars —Context Variables. +https://docs.python.org/3/library/contextvars.html.

    +
    +
    12
    +

    Fantix King. gino/aiocontextvars.py. +https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py.

    +
    +
    13
    +

    Fantix King. 数据库事务. +https://python-gino.org/docs/zh/master/how-to/transaction.html.

    +
    +
    14
    +

    Fantix King. 引擎与连接. +https://python-gino.org/docs/zh/master/explanation/engine.html.

    +
    +
    15
    +

    GINO Community. GINO Community. https://github.com/python-gino/.

    +
    +
    16
    +

    aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/.

    +
    +
    17
    +

    Sanic Community. SanicFramework. https://sanicframework.org/.

    +
    +
    18
    +

    Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/.

    +
    +
    19
    +

    Encode OSS. Starlette. https://www.starlette.io/.

    +
    +
    20
    +

    Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/.

    +
    +
    21
    +

    Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. +https://github.com/CanopyTax/asyncpgsa.

    +
    +
    22
    +

    Fantix King. 表结构定义 -GINO 引擎. +https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine.

    +
    +
    23
    +

    Fantix King. 表结构定义 - GINO core. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core.

    +
    +
    24
    +

    Fantix King. 表结构定义 - GINO ORM. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm.

    +
    +
    25
    +

    MagicStack Inc. +asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. +https://github.com/MagicStack/asyncpg.

    +
    +
    26
    +

    MagicStack Inc. uvloop -Ultra fast asyncio event loop. +https://github.com/MagicStack/uvloop.

    +
    +
    27
    +

    Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/.

    +
    +
    28
    +

    Wikipedia. Plain old Java object. +https://en.wikipedia.org/wiki/Plain_old_Java_object.

    +
    +
    29
    +

    Fantix King. 加载器与关系. +https://python-gino.org/docs/zh/master/how-to/loaders.html.

    +
    +
    30
    +

    Andrey Bondar. Tortoise ORM. https://tortoise.github.io/.

    +
    +
    31
    +

    Encode OSS. ORM - An async ORM. https://github.com/encode/orm.

    +
    +
    32
    +

    Fantix King. 欢迎来到 GINO 的文档!. +https://python-gino.org/docs/zh/master/index.html.

    +
    +
    33
    +

    JetBrains s.r.o. Open Source Licenses. +https://www.jetbrains.com/community/opensource/#support.

    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/tutorials/fastapi.html b/docs/zh/1.0/tutorials/fastapi.html new file mode 100644 index 0000000..e53ea41 --- /dev/null +++ b/docs/zh/1.0/tutorials/fastapi.html @@ -0,0 +1,665 @@ + + + + + + + + 搭建一个 FastAPI 服务器 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    搭建一个 FastAPI 服务器

    +

    在这篇教程里,我们会一起搭建一个用于生产环境的 FastAPI 服务器。完整的示例代码在这里

    +

    写好之后,整个应用技术栈会是这样的:

    +../_images/gino-fastapi.svg
    +

    创建一个新项目

    +

    这里我们尝试用亮瞎眼的 Poetry 来管理我们的项目,而不是传统的 pip。请跟随链接安装 Poetry,并且在一个空文件夹中创建我们的新项目:

    +
    $ mkdir gino-fastapi-demo
    +$ cd gino-fastapi-demo
    +$ git init
    +$ poetry init
    +
    +
    +

    然后跟着 Poetry 的向导完成初始化——关于交互式创建依赖的两个问题,您可以回答“no”,因为我们会在下面手动创建。其他问题都可以用默认值,只是一定保证包的名字是 gino-fastapi-demo

    +
    +
    +

    添加依赖关系

    +

    FastAPI 底层用的是 Starlette 框架,所以我们就可以直接使用 GINO 的 Starlette 扩展。执行以下命令即可:

    +
    $ poetry add gino[starlette]
    +
    +
    +

    接着我们添加 FastAPI,以及快成一道闪电的 ASGI 服务器 Uvicorn,还有用作生产环境的应用服务器 Gunicorn

    +
    $ poetry add fastapi uvicorn gunicorn
    +
    +
    +

    我们将用 Alembic 来管理数据库表结构变更。因为 Alembic 只兼容传统的 DB-API 驱动,所以我们还得加上 psycopg

    +
    $ poetry add alembic psycopg2
    +
    +
    +

    最后,测试框架选用 pytest,我们将其添加到开发环境的依赖关系中。同时也加上 requests 库,因为 StarletteTestClient 要用到它:

    +
    $ poetry add -D pytest requests
    +
    +
    +
    +

    提示

    +

    经过了上面的步骤,Poetry 会悄没声地帮我们自动创建一个 virtualenv,并且把所有的依赖关系装到这个虚拟环境里。在本教程后面的步骤里,我们会假定继续使用这个环境。但是,您也可以创建自己的 virtualenv,只要激活了 Poetry 就会用它。

    +
    +

    以上。下面是 Poetry 给我创建出来的 pyproject.toml 文件内容,您的应该也长得差不多:

    +
    [tool.poetry]
    +name = "gino-fastapi-demo"
    +version = "0.1.0"
    +description = ""
    +authors = ["Fantix King <fantix.king@gmail.com>"]
    +
    +[tool.poetry.dependencies]
    +python = "^3.8"
    +gino = {version = "^1.0", extras = ["starlette"]}
    +fastapi = "^0.54.1"
    +uvicorn = "^0.11.3"
    +gunicorn = "^20.0.4"
    +alembic = "^1.4.2"
    +psycopg2 = "^2.8.5"
    +
    +[tool.poetry.dev-dependencies]
    +pytest = "^5.4.1"
    +requests = "^2.23.0"
    +
    +[build-system]
    +requires = ["poetry>=0.12"]
    +build-backend = "poetry.masonry.api"
    +
    +
    +../_images/gino-fastapi-poetry.svg

    同时自动生成的还有一个叫 poetry.lock 的文件,内容是当前完整依赖关系树的精确版本号,当前的目录结构如右图所示。现在让我们把这两个文件加到 Git 仓库中(以后的步骤就不再演示 Git 的操作了):

    +
    $ git add pyproject.toml poetry.lock
    +$ git commit -m 'add project dependencies'
    +
    +
    +
    +
    +

    编写一个简单的服务器

    +

    现在让我们写一点 Python 的代码吧。

    +

    我们要创建一个 src 文件夹,用来装所有的 Python 文件,如下图所示。这种目录结构叫做“src 布局”,能让项目结构更清晰。

    +../_images/gino-fastapi-src.svg

    我们项目的顶层 Python 包叫做 gino_fastapi_demo,我们在里面创建两个 Python 模块:

    +
      +
    • asgi 作为 ASGI 的入口,将被 ASGI 服务器直接使用

    • +
    • main 用来初始化我们自己的服务器

    • +
    +

    下面是 main.py 的内容:

    +
    from fastapi import FastAPI
    +
    +def get_app():
    +    app = FastAPI(title="GINO FastAPI Demo")
    +    return app
    +
    +
    +

    asgi.py 里,我们只需要实例化我们的应用即可:

    +
    from .main import get_app
    +
    +app = get_app()
    +
    +
    +

    然后执行 poetry install 来把我们的 Python 包以开发模式链接到 PYTHONPATH 中,接下来就可以启动 Uvicorn 的开发服务器了:

    +
    $ poetry install
    +Installing dependencies from lock file
    +
    +No dependencies to install or update
    +
    +  - Installing gino-fastapi-demo (0.1.0)
    +
    +$ poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    +INFO:     Started reloader process [53010]
    +INFO:     Started server process [53015]
    +INFO:     Waiting for application startup.
    +INFO:     Application startup complete.
    +
    +
    +

    这里的 --reload 选项会启用 Uvicorn 的自动加载功能,当我们的 Python 代码发生变动的时候,Uvicorn 会自动加载使用新代码。现在可以访问 http://127.0.0.1:8000/docs 了,试一下我们新 FastAPI 服务器的 Swagger UI 接口文档。

    +
    +

    提示

    +

    正如之前提到的,如果您使用自己的虚拟环境,那么此处的 poetry run uvicorn 就可以简化为 uvicorn

    +

    poetry run 是一个快捷命令,用于在 Poetry 管理的虚拟环境中执行后续的命令。

    +
    +
    +
    +

    添加 GINO 扩展

    +../_images/gino-fastapi-config.svg

    现在让我们把 GINO 添加到服务器里。

    +

    首先,我们需要有办法来配置数据库。在本教程中,我们选用 Starlette配置系统。创建文件 src/gino_fastapi_demo/config.py,内容为:

    +
    from sqlalchemy.engine.url import URL, make_url
    +from starlette.config import Config
    +from starlette.datastructures import Secret
    +
    +config = Config(".env")
    +
    +DB_DRIVER = config("DB_DRIVER", default="postgresql")
    +DB_HOST = config("DB_HOST", default=None)
    +DB_PORT = config("DB_PORT", cast=int, default=None)
    +DB_USER = config("DB_USER", default=None)
    +DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    +DB_DATABASE = config("DB_DATABASE", default=None)
    +DB_DSN = config(
    +    "DB_DSN",
    +    cast=make_url,
    +    default=URL(
    +        drivername=DB_DRIVER,
    +        username=DB_USER,
    +        password=DB_PASSWORD,
    +        host=DB_HOST,
    +        port=DB_PORT,
    +        database=DB_DATABASE,
    +    ),
    +)
    +DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1)
    +DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16)
    +DB_ECHO = config("DB_ECHO", cast=bool, default=False)
    +DB_SSL = config("DB_SSL", default=None)
    +DB_USE_CONNECTION_FOR_REQUEST = config(
    +    "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True
    +)
    +DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1)
    +DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1)
    +
    +
    +

    这个配置文件会首先从环境变量中加载配置参数,如果没找到,则会从当前路径(通常是项目顶层目录)下一个叫 .env 的文件中加载,最后不行才会使用上面定义的默认值。比如,您即可以在命令行中设置:

    +
    $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +
    +
    +../_images/gino-fastapi-env.svg

    也可以在 .env 文件中设置(一定不要将该文件提交到 Git 中,记得在 .gitignore 里加上它):

    +
    DB_HOST=localhost
    +DB_USER=postgres
    +
    +
    +

    接下来就该创建 PostgreSQL 数据库实例并且将连接参数设置好了。创建数据库实例的命令通常是 createdb yourdbname,但不同平台可能有不同的方式,此教程里就不具体写了。

    +
    +

    小技巧

    +

    另外,您也可以使用 DB_DSN 来定义数据库连接参数,比如 postgresql://user:password@localhost:5432/dbname,它会覆盖出现在它前面的单个的配置,比如 DB_HOST

    +

    除了默认值不算之外,只要您定义了 DB_DSN ——不管是在环境变量中还是在 .env 文件中,它都比单个的连接参数有更高的优先级。比如哪怕环境变量中定义了 DB_HOST.env 文件中的 DB_DSN 仍然能够覆盖前者的值。

    +
    +../_images/gino-fastapi-models.svg

    然后创建一个 Python 的二级包 gino_fastapi_demo.models,用来封装数据库相关的代码。将下面的代码添加到 src/gino_fastapi_demo/models/__init__.py

    +
    from gino.ext.starlette import Gino
    +
    +from .. import config
    +
    +db = Gino(
    +    dsn=config.DB_DSN,
    +    pool_min_size=config.DB_POOL_MIN_SIZE,
    +    pool_max_size=config.DB_POOL_MAX_SIZE,
    +    echo=config.DB_ECHO,
    +    ssl=config.DB_SSL,
    +    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    +    retry_limit=config.DB_RETRY_LIMIT,
    +    retry_interval=config.DB_RETRY_INTERVAL,
    +)
    +
    +
    +

    最后,修改 src/gino_fastapi_demo/main.py,安装 GINO 扩展:

    +
     from fastapi import FastAPI
    ++
    ++from .models import db
    +
    + def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    ++    db.init_app(app)
    +     return app
    +
    +
    +

    保存该文件后,您应该可以看到 Uvicorn 服务器重载了我们的变更,然后连上了数据库:

    +
    WARNING:  Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading...
    +INFO:     Shutting down
    +INFO:     Waiting for application shutdown.
    +INFO:     Application shutdown complete.
    +INFO:     Finished server process [63562]
    +INFO:     Started server process [63563]
    +INFO:     Waiting for application startup.
    +INFO:     Connecting to the database: postgresql://fantix:***@localhost
    +INFO:     Database connection pool created: <asyncpg.pool.Pool max=16 min=1 cur=1 use=0>
    +INFO:     Application startup complete.
    +
    +
    +
    +
    +

    创建 model 及 API

    +../_images/gino-fastapi-models-users.svg

    现在轮到实现 API 逻辑了。比方说我们打算做一个用户管理的服务,可以添加、查看和删除用户。

    +

    首先,我们需要一张数据库表 users,用于存储数据。在 gino_fastapi_demo.models.users 模块中添加一个映射这张表的 model User

    +
    from . import db
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default="unnamed")
    +
    +
    +../_images/gino-fastapi-views.svg

    很简单的 model 定义,一切尽在不言中。

    +

    然后我们只需要在 API 的实现中正确使用它即可。创建一个新的 Python 二级包 gino_fastapi_demo.models.views,在其中添加一个模块 gino_fastapi_demo.views.users,内容为:

    +
    from fastapi import APIRouter
    +from pydantic import BaseModel
    +
    +from ..models.users import User
    +
    +router = APIRouter()
    +
    +@router.get("/users/{uid}")
    +async def get_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    return user.to_dict()
    +
    +class UserModel(BaseModel):
    +    name: str
    +
    +@router.post("/users")
    +async def add_user(user: UserModel):
    +    rv = await User.create(nickname=user.name)
    +    return rv.to_dict()
    +
    +@router.delete("/users/{uid}")
    +async def delete_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    await user.delete()
    +    return dict(id=uid)
    +
    +def init_app(app):
    +    app.include_router(router)
    +
    +
    +

    APIRouter 用来收集新接口的定义,然后在 init_app 里集成到 FastAPI 应用中去。这里我们加一点反转控制 :把接口做成模块化的,用 Entry Points 功能进行拼装,避免需要手动一一 import 将来可能有的其他接口。将下面的代码添加到 gino_fastapi_demo.main

    +
    import logging
    +from importlib.metadata import entry_points
    +
    +logger = logging.getLogger(__name__)
    +
    +def load_modules(app=None):
    +    for ep in entry_points()["gino_fastapi_demo.modules"]:
    +        logger.info("Loading module: %s", ep.name)
    +        mod = ep.load()
    +        if app:
    +            init_app = getattr(mod, "init_app", None)
    +            if init_app:
    +                init_app(app)
    +
    +
    +
    +

    提示

    +

    如果您的 Python 版本低于 3.8,您还需要这个 importlib-metadata 的移植

    +
    +

    然后在我们的应用工厂函数中调用它:

    +
     def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    +     db.init_app(app)
    ++    load_modules(app)
    +     return app
    +
    +
    +

    最后,根据 Poetry 插件文档,在 pyproject.toml 文件中定义 Entry Point:

    +
    [tool.poetry.plugins."gino_fastapi_demo.modules"]
    +"users" = "gino_fastapi_demo.views.users"
    +
    +
    +

    再执行一次 poetry install 来激活这些 Entry Point——这次您可能需要亲自重启 Uvicorn 的开发服务器了,因为自动重载机制无法识别 pyproject.toml 文件的变更。

    +

    现在您应该可以在 Swagger UI 中看到那 3 个新接口了,但是它们还都不能用,因为我们还没有创建数据库表。

    +
    +
    +

    集成 Alembic

    +

    请在项目顶层文件夹中执行下面的命令,以开始使用 Alembic

    +
    $ poetry run alembic init migrations
    +
    +
    +../_images/gino-fastapi-alembic.svg

    这句命令会生产一个新的文件夹 migrations,包含了 Alembic 用于数据库表结构变更追踪的版本文件。同时创建的还有一个在顶层文件夹下面的 alembic.ini 文件,我们把这些文件都添加到 Git 中。

    +

    为了能让 Alembic 用上我们用 GINO 定义的 model,我们需要修改 migrations/env.py 文件去链接 GINO 实例:

    +
     # add your model's MetaData object here
    + # for 'autogenerate' support
    + # from myapp import mymodel
    + # target_metadata = mymodel.Base.metadata
    +-target_metadata = None
    ++from gino_fastapi_demo.config import DB_DSN
    ++from gino_fastapi_demo.main import db, load_modules
    ++
    ++load_modules()
    ++config.set_main_option("sqlalchemy.url", str(DB_DSN))
    ++target_metadata = db
    +
    +
    +

    然后就可以创建我们的第一个变更版本了:

    +
    $ poetry run alembic revision --autogenerate -m 'add users table'
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.autogenerate.compare] Detected added table 'users'
    +  Generating migrations/versions/32c0feba61ea_add_users_table.py ...  done
    +
    +
    +

    生成的版本文件大体上应该长这样:

    +
    def upgrade():
    +    op.create_table(
    +        "users",
    +        sa.Column("id", sa.BigInteger(), nullable=False),
    +        sa.Column("nickname", sa.Unicode(), nullable=True),
    +        sa.PrimaryKeyConstraint("id"),
    +    )
    +
    +def downgrade():
    +    op.drop_table("users")
    +
    +
    +
    +

    提示

    +

    以后需要再次修改数据库表结构的时候,您只需要修改 GINO model 然后执行 alembic revision --autogenerate 命令来生成对应改动的新版本即可。提交前记得看一下生成的版本文件,有时需要调整。

    +
    +

    我们终于可以应用此次变更了,执行下面的命令将数据库表结构版本升级至最高:

    +
    $ poetry run alembic upgrade head
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.runtime.migration] Running upgrade  -> 32c0feba61ea, add users table
    +
    +
    +

    到这里,所有的接口应该都可以正常工作了,您可以在 Swagger UI 中试一下。

    +
    +
    +

    编写测试

    +

    为了不影响开发环境的数据库,我们需要为测试创建单独的数据库。根据下面的补丁修改 gino_fastapi_demo.config

    +
     config = Config(".env")
    +
    ++TESTING = config("TESTING", cast=bool, default=False)
    +
    + DB_DRIVER = config("DB_DRIVER", default="postgresql")
    + DB_HOST = config("DB_HOST", default=None)
    + DB_PORT = config("DB_PORT", cast=int, default=None)
    + DB_USER = config("DB_USER", default=None)
    + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    + DB_DATABASE = config("DB_DATABASE", default=None)
    ++if TESTING:
    ++    if DB_DATABASE:
    ++        DB_DATABASE += "_test"
    ++    else:
    ++        DB_DATABASE = "gino_fastapi_demo_test"
    + DB_DSN = config(
    +
    +
    +
    +

    提示

    +

    您需要执行 createdb 来创建数据库实例。比如说,如果您在 .env 文件中定义了 DB_DATABASE=mydb,那么测试数据库的名字就是 mydb_test。否则如果没定义的话,默认就是 gino_fastapi_demo_test

    +
    +

    然后在 tests/conftest.py 中创建 pytest fixture:

    +
    import pytest
    +from alembic.config import main
    +from starlette.config import environ
    +from starlette.testclient import TestClient
    +
    +environ["TESTING"] = "TRUE"
    +
    +@pytest.fixture
    +def client():
    +    from gino_fastapi_demo.main import db, get_app
    +
    +    main(["--raiseerr", "upgrade", "head"])
    +
    +    with TestClient(get_app()) as client:
    +        yield client
    +
    +    main(["--raiseerr", "downgrade", "base"])
    +
    +
    +../_images/gino-fastapi-tests.svg

    这个 fixture 的作用是,在跑测试之前创建所有的数据库表、提供一个 StarletteTestClient、并且在测试跑完之后删除所有的表及其数据,为后续测试保持一个干净的环境。

    +

    下面是一个简单的测试例子,tests/test_users.py

    +
    import uuid
    +
    +def test_crud(client):
    +    # create
    +    nickname = str(uuid.uuid4())
    +    r = client.post("/users", json=dict(name=nickname))
    +    r.raise_for_status()
    +
    +    # retrieve
    +    url = f"/users/{r.json()['id']}"
    +    assert client.get(url).json()["nickname"] == nickname
    +
    +    # delete
    +    client.delete(url).raise_for_status()
    +    assert client.get(url).status_code == 404
    +
    +
    +

    测试跑起来:

    +
    $ poetry run pytest
    +=========================== test session starts ===========================
    +platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    +rootdir: gino-fastapi-demo
    +collected 1 item
    +
    +tests/test_users.py .                                               [100%]
    +
    +============================ 1 passed in 1.21s ============================
    +
    +
    +
    +
    +

    生产环境注意事项

    +

    最近 Docker/Kubernetes 挺火,我们也写一个 Dockerfile

    +
    FROM python:3.8-alpine as base
    +
    +FROM base as builder
    +RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev
    +RUN pip install poetry
    +COPY . /src/
    +WORKDIR /src
    +RUN python -m venv /env && . /env/bin/activate && poetry install
    +
    +FROM base
    +RUN apk add --no-cache postgresql-libs
    +COPY --from=builder /env /env
    +COPY --from=builder /src /src
    +WORKDIR /src
    +CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"]
    +
    +
    +

    这个 Dockerfile 里,为了降低目标镜像文件的大小,我们分成了两步来分别进行源码构建和生产镜像的组装。另外,我们还采用了 Gunicorn 搭配 UvicornUvicornWorker 的方式来获取最佳生产级别可靠性。

    +

    回头看一下项目里一共有哪些文件。

    +../_images/gino-fastapi-layout.svg

    至此,我们就完成了演示项目的开发。下面是上生产可以用到的一个不完整检查清单:

    +
      +
    • DB_RETRY_LIMIT 设置成一个稍微大一点的数字,以支持在数据库就绪前启动应用服务器的情况。

    • +
    • migrations/env.py 中实现同样的重连尝试逻辑,这样 Alembic 也能拥有同样的特性。

    • +
    • 如果需要的话,启用 DB_SSL

    • +
    • 写一个 docker-compose.yml,用于其他开发人员快速尝鲜,甚至可以用于开发。

    • +
    • 启用持续集成 <CI_>,安装 pytest-cov 并且用 --cov-fail-under 参数来保障测试覆盖率。

    • +
    • 集成静态代码检查工具和安全性/CVE筛查工具。

    • +
    • 正确自动化 Alembic 的升级流程,比如在每次新版本部署之后执行。

    • +
    • 注意针对诸如 CSRFXSS 等常见攻击的安全性防护。

    • +
    • 编写压力测试。

    • +
    +

    最后再贴一次,实例程序的源码在这里,本教程的文档源码在`这里<https://github.com/python-gino/gino/blob/master/docs/tutorials/fastapi.rst>`__,请敞开了提 PR,修问题或者分享想法都行。祝玩得愉快!

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.0/tutorials/tutorial.html b/docs/zh/1.0/tutorials/tutorial.html new file mode 100644 index 0000000..45b6009 --- /dev/null +++ b/docs/zh/1.0/tutorials/tutorial.html @@ -0,0 +1,484 @@ + + + + + + + + GINO 基础教程 - GINO 1.0.2.dev0 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    GINO 基础教程

    +

    这是一篇写给刚入坑同学的指南,将介绍 GINO 的基本部分。阅读之前,请先了解以下知识点:

    + +

    您不需要对 SQLAlchemy 有所了解。

    +
    +

    介绍

    +

    简单来说,GINO 可以在您的异步应用中帮助您完成 SQL 语句的生成及执行,您只需要通过友好的对象化 API 来操作您的数据即可,无需亲自编写 SQL 与数据库交互。

    +

    因为异步编程并不会使您的程序变快——如果说不拖累的话——而且还会增加复杂度和风险,所以也许您并不需要 GINO 或者说是异步数据库连接。跳坑之前请先阅读为什么要用异步 ORM?

    +
    +
    +

    安装

    +

    请在终端中执行以下命令以安装 GINO:

    +
    $ pip install gino
    +
    +
    +

    以上就是安装 GINO 的推荐方式,因为这种方式始终会去安装最新的稳定版。

    +

    如果您还没有安装过 pip,您可以参阅 Python 安装指南

    +

    另外如果您在使用 Poetry 进行项目依赖关系管理,那需要执行的则是:

    +
    $ poetry add gino
    +
    +
    +
    +
    +

    声明模型

    +

    开始之前,我们需要先创建一个 Gino 的全局实例,通常叫做 db

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +

    db 可以被当做是数据库的一个代表,后续大部分的数据库交互都将通过它来完成。

    +

    “Model” 是 GINO 中的一个基本概念,它表示继承自 db.Model 的用户定义类。每个 Model 的子类代表了数据库中的一张表,而这些类的对象则代表了对应表中的一行数据。如果您曾经使用过其它 ORM 产品,对这种映射关系应该不感到陌生。现在我们尝试定义一个 model:

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +

    这里的 User 类其实就是在定义一张叫做 users 的数据库表,包含了 idnickname 两个字段。请注意,__tablename__ 是一个必要的固定属性。GINO 建议使用单数名词来为 model 命名,同时使用复数名词去命名表。每个 db.Column 属性都定义了一个数据库字段,其中第一个参数是字段类型,其余参数则用来定义字段其他属性或约束。您可以参考 SQLAlchemy 的文档来了解不同 db 类型到数据库类型的对应关系。

    +
    +

    注解

    +

    SQLAlchemy 是 Python 中一个强大的非异步 ORM 库,而 GINO 就是基于其构建的。通过不同的 SQL 方言实现,SQLAlchemy 支持包括 PostgreSQL 和 MySQL 在内的许多流行的 RDBMS,以至于有时相同的 Python 代码可以不经修改地运行在不同的数据库上。GINO 自然也承袭了这一特性,但目前暂仅支持 PostgreSQL(通过 asyncpg)。

    +
    +

    如果需要定义涵盖多个列的数据库约束或索引,您仍然可以通过 model 类属性的方式来定义,属性名称虽未被用到,但不能重复。例如:

    +
    class Booking(db.Model):
    +    __tablename__ = 'bookings'
    +
    +   day = db.Column(db.Date)
    +   booker = db.Column(db.String)
    +   room = db.Column(db.String)
    +
    +   _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey')
    +   _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True)
    +   _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room')
    +
    +
    +

    另外如果有倾向性,您也可以在 model 类之外定义约束和索引,请参考 SQLAlchemy 文档来了解更多细节。

    +

    由于一些限制,目前不允许在父类中直接使用类属性的方式来单独定义数据库约束和索引,__table_args__ 也是一样的。GINO 提供了 declared_attr() 来实现比如 mixin 类这样的功能,更多信息请参阅其 API 文档。

    +
    +
    +

    建立连接

    +

    前面的声明只是定义了映射关系,并非实际在数据库中创建了这些表结构。为了使用 GINO 来创建表,我们需要先与数据库建立连接。这里我们先为本指南创建一个 PostgreSQL 的数据库实例:

    +
    $ createdb gino
    +
    +
    +

    然后,告诉我们的 db 对象去连接这个数据库:

    +
    import asyncio
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +

    如果执行成功了,那就意味着您连上了新创建的数据库。此处的 postgresql 代表了要用的数据库方言(默认的驱动是 asyncpg,您也可以显式地指定使用它:postgresql+asyncpg:// 或者就只写 asyncpg://),localhost 是数据库服务器所在的地址,gino 是数据库实例的名字。这里可以读到更多关于如何构造一个数据库 URL 的信息。

    +
    +

    注解

    +

    在底层,set_bind() 调用了 create_engine() 来创建 engine,并将其绑定到 db 对象上。GINO engine 与 SQLAlchemy engine 类似,但 GINO engine 是异步的,而后者是阻塞式的。关于如何使用 engine,请参考 GINO 的 API 文档。

    +
    +

    建立连接之后,我们就可以用 GINO 在数据库中创建我们的表了(在同一个 main() 函数里):

    +
    await db.gino.create_all()
    +
    +
    +
    +

    警告

    +

    这里是 db.gino.create_all,而不是 db.create_all,因为 db 继承自 SQLAlchemy 的 MetaData,而 db.create_all 是 SQLAlchemy 的阻塞式方法,无法适用于绑定的 GINO engine。

    +

    实践中 create_all() 通常并不是一个理想的解决方案。为了管理数据库表结构,我们通常推荐使用诸如 Alembic 这样的工具,请参阅如何 使用 Alembic

    +
    +

    如果您想显式地断开与数据库的连接,您可以这么做:

    +
    await db.pop_bind().close()
    +
    +
    +

    继续之前,让我们重新看一下前面所有的代码:

    +
    import asyncio
    +from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +    await db.gino.create_all()
    +
    +    # further code goes here
    +
    +    await db.pop_bind().close()
    +
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +
    +
    +

    增删改查

    +

    为了操作数据库中的数据,GINO 提供了基本的基于对象的增删改查功能。

    +
    +

    +

    让我们从创建一个 User 对象开始:

    +
    user = await User.create(nickname='fantix')
    +# This will cause GINO to execute this SQL with parameter 'fantix':
    +# INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname
    +
    +
    +

    正如之前所说,user 对象代表了数据库中新插入的这一行数据。您可以通过 user 对象上的之前定义的列属性来访问每一列的值:

    +
    print(f'ID:       {user.id}')           # 1
    +print(f'Nickname: {user.nickname}')     # fantix
    +
    +
    +

    另外,您也可以先在内存中创建一个 user 对象,然后再将其插入到数据库中:

    +
    user = User(nickname='fantix')
    +user.nickname += ' (founder)'
    +await user.create()
    +
    +
    +
    +
    +

    +

    想要通过主键来获取一个 model 对象,您可以使用 model 的类方法 get()。比如,重新获取刚才插入的同一行数据:

    +
    user = await User.get(1)
    +# SQL (parameter: 1):
    +# SELECT users.id, users.nickname FROM users WHERE users.id = $1
    +
    +
    +

    常规的 SQL 查询则是通过类属性 query 来完成。比如,获取数据库中所有的 User 对象的列表:

    +
    all_users = await db.all(User.query)
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +

    或者,您也可以使用 querygino 扩展。比如,下面的代码可以实现一样的效果:

    +
    all_users = await User.query.gino.all()
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +
    +

    注解

    +

    实际上,User.query 是一个普通的 SQLAlchemy 查询对象,SQLAlchemy 的阻塞式执行方法依然存在其上,因此 GINO 向所有 SQLAlchemy 的“Executable”对象注入了一个 gino 扩展,以便在不影响 SQLAlchemy 原有 API 的基础上,让直接异步地执行这些查询对象更容易,而不用每次都通过 engine 或 db 对象来执行。

    +
    +

    现在让我们尝试增加一些过滤器。比如,查找出所有 ID 小于 10 的用户:

    +
    founding_users = await User.query.where(User.id < 10).gino.all()
    +# SQL (parameter: 10):
    +# SELECT users.id, users.nickname FROM users WHERE users.id < $1
    +
    +
    +

    因为查询对象就是出自于 SQLAlchemy core,所以请参阅如何编写查询

    +
    +

    警告

    +

    当您拿到一个 model 对象时,这个对象就已经彻底与数据库分离了,完全成为内存中的一个普通对象。这就意味着,即使数据库中对应的行发生了变化,对象的值仍然不会受到丝毫影响。类似地,如果您修改了该对象的值,数据库也不会受到任何影响。

    +

    并且,GINO 也不会追踪 model 对象,因此重复查询同一行数据将会得到两个独立的、拥有相同值的对象,修改其中一个的值不会幽灵般地影响到另一个的值。

    +

    不同于传统 ORM 的 model 对象通常是有状态的,GINO 的 model 对象则更像是用对象封装的 SQL 查询结果,这是 GINO 为了适应异步编程而特意设计的简易性,也是“GINO 不是 ORM”名字的来源。

    +
    +

    有时我们仅需要获取一个对象,比如验证登录时,使用用户名来查找一个用户。这时,可以使用这种便捷的写法:

    +
    user = await User.query.where(User.nickname == 'fantix').gino.first()
    +# SQL (parameter: 'fantix'):
    +# SELECT users.id, users.nickname FROM users WHERE users.nickname = $1
    +
    +
    +

    如果数据库中没有叫“fantix”的用户,则 user 会被置为 None

    +

    又有时,我们会需要获取一个单独的值,比如 ID 为 1 的用户的名字。此时可以使用 model 的类方法 select()

    +
    name = await User.select('nickname').where(User.id == 1).gino.scalar()
    +# SQL (parameter: 1):
    +# SELECT users.nickname FROM users WHERE users.id = $1
    +print(name)  # fantix
    +
    +
    +

    又比如,查询用户数量:

    +
    population = await db.func.count(User.id).gino.scalar()
    +# SQL:
    +# SELECT count(users.id) AS count_1 FROM users
    +print(population)  # 17 for example
    +
    +
    +
    +
    +

    +

    接下来,让我们尝试对数据做一些修改,下面的例子会穿插一些前面用过的查询操作。

    +
    # create a new user
    +user = await User.create(nickname='fantix')
    +
    +# get its name
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +assert name == user.nickname  # they are both 'fantix' before the update
    +
    +# modification here
    +await user.update(nickname='daisy').apply()
    +# SQL (parameters: 'daisy', 1):
    +# UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname
    +print(user.nickname)  # daisy
    +
    +# get its name again
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +print(name)  # daisy
    +assert name == user.nickname  # they are both 'daisy' after the update
    +
    +
    +

    这里的 update() 是我们碰到的第一个 model 实例上的 GINO 方法,它接受多个自定义命名参数,参数名对应着 model 的字段名,而参数值则为期望修改成的新的值。连着写的 apply() 则会将这一修改同步到数据库中。

    +
    +

    注解

    +

    GINO 显式地将“修改内存中对象的值”与“修改数据库中的行”拆分成了两个方法: update()apply()update() 负责修改内存中的值,并且将改动记录在返回的 UpdateRequest 对象中;紧接着调用的 UpdateRequest 对象的 apply() 方法则会将这些记录下的改动通过 SQL 更新到数据库中。

    +
    +
    +

    小技巧

    +

    UpdateRequest 对象还有一个方法也叫 update(),它与 model 对象上的 update() 方法的功能是一样的,只不过前者还会将新的改动记录与当前 UpdateRequest 已记录的改动合并在一起,并且返回同一个 UpdateRequest 对象。这意味着,您可以连着写多个 update() 调用,最后用一个 apply() 结尾,或者仅仅是通过 UpdateRequest 对象来完成内存对象的多次改动。

    +
    +

    Model 对象上的 update() 方法只能操作该对象对应的数据库中的一行数据,而如果您想要批量更新多行数据的话,您可以使用 model 类上的 update() 类方法。用法略有不同:

    +
    await User.update.values(nickname='Founding Member ' + User.nickname).where(
    +    User.id < 10).gino.status()
    +# SQL (parameter: 'Founding Member ', 10):
    +# UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2
    +
    +name = await User.select('nickname').where(
    +    User.id == 1).gino.scalar()
    +print(name)  # Founding Member fantix
    +
    +
    +

    这里不再有 UpdateRequest 了,所有的操作又回到了普通的 SQLAlchemy 用法,更多细节可以参考 SQLAlchemy 的文档

    +
    +
    +

    +

    最后,删除一行数据与更新一行数据有些类似,但要简单很多:

    +
    user = await User.create(nickname='fantix')
    +await user.delete()
    +# SQL (parameter: 1):
    +# DELETE FROM users WHERE users.id = $1
    +print(await User.get(user.id))  # None
    +
    +
    +
    +

    提示

    +

    还记得内存对象的事情吗?在最后一行的 print() 中,尽管数据库中已经没有这一行数据了,但是 user 对象依然在内存中,它的值也都没有变化,所以这里仍然可以用 user.id

    +
    +

    或者批量删除(千万不要忘记写 where!是不是整个表都不想要了?):

    +
    await User.delete.where(User.id > 10).gino.status()
    +# SQL (parameter: 10):
    +# DELETE FROM users WHERE users.id > $1
    +
    +
    +

    有了基本的 增删改查,您应该已经可以用 GINO 做出一些不可思议的东西来了。这篇上手指南到此结束,要了解更多请继续阅读文档的剩余部分。祝编程愉快!

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/.buildinfo b/docs/zh/1.1b2/.buildinfo new file mode 100644 index 0000000..726728b --- /dev/null +++ b/docs/zh/1.1b2/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: ac2ff5533d162f4d1c6340a10eec5293 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/zh/1.1b2/_images/263px-Minimum-Tonne.svg.png b/docs/zh/1.1b2/_images/263px-Minimum-Tonne.svg.png new file mode 100644 index 0000000..f431e31 Binary files /dev/null and b/docs/zh/1.1b2/_images/263px-Minimum-Tonne.svg.png differ diff --git a/docs/zh/1.1b2/_images/archlinux.webp b/docs/zh/1.1b2/_images/archlinux.webp new file mode 100644 index 0000000..3e358ef Binary files /dev/null and b/docs/zh/1.1b2/_images/archlinux.webp differ diff --git a/docs/zh/1.1b2/_images/community.svg b/docs/zh/1.1b2/_images/community.svg new file mode 100644 index 0000000..dae6507 --- /dev/null +++ b/docs/zh/1.1b2/_images/community.svg @@ -0,0 +1,26 @@ + + + + co-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/connection.png b/docs/zh/1.1b2/_images/connection.png new file mode 100644 index 0000000..e54598d Binary files /dev/null and b/docs/zh/1.1b2/_images/connection.png differ diff --git a/docs/zh/1.1b2/_images/docs.webp b/docs/zh/1.1b2/_images/docs.webp new file mode 100644 index 0000000..20259b1 Binary files /dev/null and b/docs/zh/1.1b2/_images/docs.webp differ diff --git a/docs/zh/1.1b2/_images/engine.png b/docs/zh/1.1b2/_images/engine.png new file mode 100644 index 0000000..6b64561 Binary files /dev/null and b/docs/zh/1.1b2/_images/engine.png differ diff --git a/docs/zh/1.1b2/_images/exchangeratesapi.webp b/docs/zh/1.1b2/_images/exchangeratesapi.webp new file mode 100644 index 0000000..dc2657a Binary files /dev/null and b/docs/zh/1.1b2/_images/exchangeratesapi.webp differ diff --git a/docs/zh/1.1b2/_images/explanation.svg b/docs/zh/1.1b2/_images/explanation.svg new file mode 100644 index 0000000..ef775d1 --- /dev/null +++ b/docs/zh/1.1b2/_images/explanation.svg @@ -0,0 +1,19 @@ + + + + ex-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-alembic.svg b/docs/zh/1.1b2/_images/gino-fastapi-alembic.svg new file mode 100644 index 0000000..0cfc4b1 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-alembic.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    alembic.ini
    ale...
    migrations
    migra...
    env.py
    env...
    versions
    versi...
    32c0feba61ea_add_users_table.py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-config.svg b/docs/zh/1.1b2/_images/gino-fastapi-config.svg new file mode 100644 index 0000000..673d084 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-config.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    config.py
    con...
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-env.svg b/docs/zh/1.1b2/_images/gino-fastapi-env.svg new file mode 100644 index 0000000..4ec9e4e --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-env.svg @@ -0,0 +1,3 @@ + + +
    .env
    .env
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-layout.svg b/docs/zh/1.1b2/_images/gino-fastapi-layout.svg new file mode 100644 index 0000000..07f4701 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-layout.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    .env
    .env
    models
    models
    __init__.py
    __i...
    users.py
    use...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    __init__.py
    __i...
    asgi.py
    asg...
    config.py
    con...
    main.py
    mai...
    tests
    tests
    conftest.py
    con...
    test_users.py
    tes...
    migrations
    migra...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    The project root directory.

    Alembic data directory.

    Database migration revisions directory.

    One of the revisions.

    Alembic Python environment.

    Application source code container.

    Project root python package.

    Database models and GINO instance.

    GINO instance (SQLAlchemy Metadata).

    Models for users.

    API implementation.



    User-related APIs.



    ASGI entry point.

    Starlette-style application configuration.

    Application initialization.

    Testing code.

    pytest fixtures.

    User-related tests.

    Local config, not in Git control.

    Alembic entry config.

    Production Docker image.

    Poetry-frozen dependency versions.

    Project and dependency definition.
    The project root directory....
    alembic.ini
    ale...
    Dockerfile
    Doc...
    env.py
    env...
    versions
    versi...
    32c0feba61ea....py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-models-users.svg b/docs/zh/1.1b2/_images/gino-fastapi-models-users.svg new file mode 100644 index 0000000..886d581 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-models-users.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-models.svg b/docs/zh/1.1b2/_images/gino-fastapi-models.svg new file mode 100644 index 0000000..4dc983c --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-models.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-poetry.svg b/docs/zh/1.1b2/_images/gino-fastapi-poetry.svg new file mode 100644 index 0000000..e0da8c1 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-poetry.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-src.svg b/docs/zh/1.1b2/_images/gino-fastapi-src.svg new file mode 100644 index 0000000..7310163 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-src.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    __init__.py
    __i...
    asgi.py
    asg...
    main.py
    mai...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-tests.svg b/docs/zh/1.1b2/_images/gino-fastapi-tests.svg new file mode 100644 index 0000000..53673d8 --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-tests.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    tests
    tests
    test_users.py
    tes...
    conftest.py
    con...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi-views.svg b/docs/zh/1.1b2/_images/gino-fastapi-views.svg new file mode 100644 index 0000000..7321a7a --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi-views.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/gino-fastapi.svg b/docs/zh/1.1b2/_images/gino-fastapi.svg new file mode 100644 index 0000000..9d365ba --- /dev/null +++ b/docs/zh/1.1b2/_images/gino-fastapi.svg @@ -0,0 +1,3 @@ + + +
    Gunicorn
    Gunicorn
    Uvicorn
    Uvicorn
    Starlette
    Starlette
    FastAPI
    FastAPI
    API Implementation
    API Implementation
    GINO Models
    GINO Models
    GINO with Starlette
    GINO with Starlette
    SQLAlchemy core
    SQLAlchemy core
    asyncpg
    asyncpg
    Alembic
    Alembic
    psycopg2
    psycopg2
    PostgreSQL
    PostgreSQL
    Application Server
    Application Server
    ASGI Middleware
    ASGI Middleware
    Web Framework
    Web Framework
    Tutorial Code
    Tutorial Code
    GINO
    GINO
    Database Library
    Database Library
    HTTP API
    HTTP API
    DB Migration CLI
    DB Migration CLI
    Legend
    Legend
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/github.svg b/docs/zh/1.1b2/_images/github.svg new file mode 100644 index 0000000..cb2484b --- /dev/null +++ b/docs/zh/1.1b2/_images/github.svg @@ -0,0 +1,27 @@ + + + + sourcecode + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/happy-hacking.png b/docs/zh/1.1b2/_images/happy-hacking.png new file mode 100644 index 0000000..701e7bd Binary files /dev/null and b/docs/zh/1.1b2/_images/happy-hacking.png differ diff --git a/docs/zh/1.1b2/_images/how-to.svg b/docs/zh/1.1b2/_images/how-to.svg new file mode 100644 index 0000000..7461024 --- /dev/null +++ b/docs/zh/1.1b2/_images/how-to.svg @@ -0,0 +1,22 @@ + + + + guide + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/open-source.svg b/docs/zh/1.1b2/_images/open-source.svg new file mode 100644 index 0000000..0946f0a --- /dev/null +++ b/docs/zh/1.1b2/_images/open-source.svg @@ -0,0 +1,18 @@ + + + + bsd + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/python-gino.webp b/docs/zh/1.1b2/_images/python-gino.webp new file mode 100644 index 0000000..f6c11ac Binary files /dev/null and b/docs/zh/1.1b2/_images/python-gino.webp differ diff --git a/docs/zh/1.1b2/_images/python.svg b/docs/zh/1.1b2/_images/python.svg new file mode 100644 index 0000000..f62b29b --- /dev/null +++ b/docs/zh/1.1b2/_images/python.svg @@ -0,0 +1,22 @@ + + + + download + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/reference.svg b/docs/zh/1.1b2/_images/reference.svg new file mode 100644 index 0000000..0c5f656 --- /dev/null +++ b/docs/zh/1.1b2/_images/reference.svg @@ -0,0 +1,19 @@ + + + + re-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/tutorials.svg b/docs/zh/1.1b2/_images/tutorials.svg new file mode 100644 index 0000000..2eb2111 --- /dev/null +++ b/docs/zh/1.1b2/_images/tutorials.svg @@ -0,0 +1,26 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_images/why_coroutine.png b/docs/zh/1.1b2/_images/why_coroutine.png new file mode 100644 index 0000000..99c743a Binary files /dev/null and b/docs/zh/1.1b2/_images/why_coroutine.png differ diff --git a/docs/zh/1.1b2/_images/why_multicore.png b/docs/zh/1.1b2/_images/why_multicore.png new file mode 100644 index 0000000..c209a9f Binary files /dev/null and b/docs/zh/1.1b2/_images/why_multicore.png differ diff --git a/docs/zh/1.1b2/_images/why_multithreading.png b/docs/zh/1.1b2/_images/why_multithreading.png new file mode 100644 index 0000000..e0e4321 Binary files /dev/null and b/docs/zh/1.1b2/_images/why_multithreading.png differ diff --git a/docs/zh/1.1b2/_images/why_single_task.png b/docs/zh/1.1b2/_images/why_single_task.png new file mode 100644 index 0000000..54fab08 Binary files /dev/null and b/docs/zh/1.1b2/_images/why_single_task.png differ diff --git a/docs/zh/1.1b2/_images/why_throughput.png b/docs/zh/1.1b2/_images/why_throughput.png new file mode 100644 index 0000000..388f533 Binary files /dev/null and b/docs/zh/1.1b2/_images/why_throughput.png differ diff --git a/docs/zh/1.1b2/_sources/explanation.rst.txt b/docs/zh/1.1b2/_sources/explanation.rst.txt new file mode 100644 index 0000000..6d91357 --- /dev/null +++ b/docs/zh/1.1b2/_sources/explanation.rst.txt @@ -0,0 +1,7 @@ +Explanation +=========== + +.. toctree:: + :glob: + + explanation/* diff --git a/docs/zh/1.1b2/_sources/explanation/async.rst.txt b/docs/zh/1.1b2/_sources/explanation/async.rst.txt new file mode 100644 index 0000000..a857170 --- /dev/null +++ b/docs/zh/1.1b2/_sources/explanation/async.rst.txt @@ -0,0 +1,214 @@ +Asynchronous Programming 101 +============================ + + +The Story +--------- + +Let's say we want to build a search engine. We'll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this: + +.. image:: ../images/why_single_task.png + :align: center + +We have lots of web pages to index, so we simply handle them one by one: + +.. image:: ../images/why_throughput.png + :align: center + +Let's assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores: + +.. image:: ../images/why_multicore.png + :align: center + +This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading: + +.. image:: ../images/why_multithreading.png + :align: center + +Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What's wrong with multi-threading? From the diagram we can see: + +* There are yellow bars taking up extra time. +* The green bars can still overlap with any bar in the other thread, but +* non-green bars cannot overlap with non-green bars in the other thread. + +The yellow bars are time taken by `context switches +`_, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let's assume a world without +`Hyper-threading `_ or similar), +so in order to run several threads concurrently the CPU must `split its +time `_ into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point. + +Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it's waiting for the HTTP response (I/O). That's how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won't be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That's also why this is called concurrency instead of parallelism. + +As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous `C10k +problem `_, usually solved by +asynchronous I/O: + +.. image:: ../images/why_coroutine.png + :align: center + +.. note:: + + Asynchronous I/O and coroutines are two different things, but they usually + go together. Here we will stick with coroutines for simplicity. + +Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem. + + +Cooperative multitasking +------------------------ + +So what is a coroutine? + +In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread. + +Threads are scheduled by the operating system using an approach called `preemptive +multitasking `_. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn't +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this: + +.. code-block:: none + + Thread 1: I wanna run! + OS: Okay, here you go... + Thread 2: I wanna run! + OS: Urh, alright one sec ... Thread 1, hold on for a while! + Thread 1: Well I'm not done yet, but you are the boss. + OS: It won't be long. Thread 2 it's your turn now. + Thread 2: Yay! (&%#$@..+*&#) + Thread 1: Can I run now? + OS: Just a moment please ... Thread 2, give it a break! + Thread 2: Alright ... but I really need the CPU. + OS: You'll have it later. Thread 1, hurry up! + +In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don't - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +`cooperative multitasking +`_. It's like this: + +.. code-block:: none + + Coroutine 1: Let me know when event A arrives. I'm done here before that. + Event manager: Okay. What about you, coroutine 2? + Coroutine 2: Um I've got nothing to do here before event B. + Event manager: Cool, I'll be watching. + Event manager: (after a while) Hey coroutine 1, event A is here! + Coroutine 1: Awesome! Let me see ... looks good, but I need event C now. + Event manager: Very well. Seems event B arrived just now, coroutine 2? + Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done. + Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while. + (silence) + Event manager: Damn, event C timed out! + Coroutine 1: Arrrrh gotta kill myself with an exception :S + Event manager: Up to you :/ + +For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That's why in the last diagram, +the red bars are not interleaved like threads. + +.. tip:: + + In Python and asyncio, ``async def`` declares coroutines, ``await`` yields + control to event loop (event manager). + + +Pros and cons +------------- + +Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput. + +With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code. + +However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple ``recv()`` operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to ``recv()``, repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop_ this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O. + +Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, ``sleep(1)`` +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between ``await`` finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind. + +Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It's not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure. + + +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/zh/1.1b2/_sources/explanation/engine.rst.txt b/docs/zh/1.1b2/_sources/explanation/engine.rst.txt new file mode 100644 index 0000000..ebd59ed --- /dev/null +++ b/docs/zh/1.1b2/_sources/explanation/engine.rst.txt @@ -0,0 +1,521 @@ +===================== +Engine and Connection +===================== + +:class:`~gino.engine.GinoEngine` is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together: + +.. image:: ../images/engine.png + :align: center + +Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users. + +During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use. + +.. note:: + + In SQLAlchemy, database drivers are supposed to follow the DB-API standard, + which does not usually provide a pool implementation. Therefore, SQLAlchemy + has its own pool implementation, created directly in engine. This is where + this diagram doesn't fit SQLAlchemy. + +The pool creates raw connections, not the :class:`~gino.engine.GinoConnection` +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries. + +On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use. + +.. note:: + + Another difference to SQLAlchemy here: GINO execution methods always return + final results, while in SQLAlchemy accessing the result may cause further + implicit database accesses. Therefore GINO engine immediately releases the + connection when the execution method on the engine returns, but SQLAlchemy + can only release the connection implicitly when the result data is found + exhausted. + + By immediately releasing a connection, GINO may not release the related raw + connection when the raw connection was reused from another parent + connection. We'll get to this later. + +GINO also supports `implicit execution +`_ +without having to specify an engine or connection explicitly. This is done by +binding the engine to the ``db`` instance, also known as the +:class:`~sqlalchemy.schema.MetaData` or the :class:`~gino.api.Gino` instance. +You may possibly bind a :class:`~gino.engine.GinoConnection` instance, but that +is greatly not recommended because it is very much untested. + +At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +``db`` instance on creation, therefore the models can do implicit execution too +if their ``db`` has a bind. + +Then let's get to some details. + + +Creating Engines +---------------- + +GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous :class:`~gino.engine.GinoEngine` is +just ``gino``, but only available after ``gino`` is imported:: + + import gino, sqlalchemy + + async def main(): + e = await sqlalchemy.create_engine('postgresql://...', strategy='gino') + # e is a GinoEngine + +.. tip:: + + Please read `this SQLAlchemy document + `_ + to learn about writing database URLs. + +Also the GINO strategy replaces the default driver of dialect ``postgresql://`` +from ``psycopg2`` to ``asyncpg``, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +``postgresql+asyncpg://...`` or just ``asyncpg://...``. + +GINO also offers a shortcut as :func:`gino.create_engine`, which only sets the +default strategy to ``gino`` and does nothing more. So here is an identical +example:: + + import gino + + async def main(): + e = await gino.create_engine('postgresql://...') + # e is also a GinoEngine + +As you may have noticed, when using the GINO strategy, +:func:`~sqlalchemy.create_engine` returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default. + +For it is just SQLAlchemy :func:`~sqlalchemy.create_engine`, the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!): + +For Dialect: + +* `isolation_level `_ +* `paramstyle `_ + +For Engine: + +* `echo `_ +* `execution_options `_ +* `logging_name `_ + +While these parameters are discarded by GINO: + +* `module `_ + +In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from :func:`~asyncpg.pool.create_pool`. +For example, we can create an engine without initial connections:: + + e = await gino.create_engine('postgresql://...', min_size=0) + +Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:: + + import sqlalchemy + + metadata = sqlalchemy.MetaData() + metadata.bind = 'postgresql://...' + + # or in short + + metadata = sqlalchemy.MetaData('postgresql://...') + +This implicitly calls :func:`~sqlalchemy.create_engine` under the hood. However +in GINO, creating an engine requires ``await``, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass :class:`~gino.api.Gino`, reverted it to simple assignment:: + + import gino + + db = gino.Gino() + + async def main(): + # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind + engine = await gino.create_engine('postgresql://...') + db.bind = engine + +And provided a shortcut to do so:: + + engine = await db.set_bind('postgresql://...') + +And another simpler shortcut for one-time usage:: + + db = await gino.Gino('postgresql://...') + +To unset a bind and close the engine:: + + engine, db.bind = db.bind, None + await engine.close() + +Or with a shortcut correspondingly:: + + await engine.pop_bind().close() + +Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +Managing Connections +-------------------- + +With a :class:`~gino.engine.GinoEngine` at hand, you can acquire connections +from the pool now:: + + conn = await engine.acquire() + +Don't forget to release it after use:: + + await conn.release() + +Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:: + + async with engine.acquire() as conn: + # play with the connection + +Here ``conn`` is a :class:`~gino.engine.GinoConnection` instance. As mentioned +previously, :class:`~gino.engine.GinoConnection` is mapped to an underlying raw +connection, as shown in following diagram: + +.. image:: ../images/connection.png + :align: center + +Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: ``reuse`` and +``lazy``. They are keyword arguments on :meth:`~gino.engine.GinoEngine.acquire` +and by default switched off. + +reuse +""""" + +When acquiring a :class:`~gino.engine.GinoConnection` (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +:class:`~gino.engine.GinoConnection` (2). This is the default behavior of +:meth:`~gino.engine.GinoConnection.acquire` with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:: + + async with engine.acquire() as conn1: + async with engine.acquire() as conn2: + # conn2 is a completely different connection than conn1 + +But sometimes ``conn2`` may exist in a different method:: + + async def outer(): + async with engine.acquire() as conn1: + await inner() + + async def inner(): + async with engine.acquire() as conn2: + # ... + +And we probably wish ``inner`` could reuse the same raw connection in +``outer`` to save some resource, or borrow a new one if ``inner`` is +individually called without ``outer``:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(conn2=None): + if conn2 is None: + async with engine.acquire() as conn2: + # ... + else: + # the same ... again + +This is exactly the scenario ``reuse`` could be useful. We can simply tell the +:meth:`~gino.engine.GinoConnection.acquire` to reuse the most recent reusable +connection in current context by setting ``reuse=True``, as presented in this +identical example:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(): + async with engine.acquire(reuse=True) as conn2: + # ... + +Back to previous diagram, the blue :class:`~gino.engine.GinoConnection` +instances (3, 4, 6) are "reusing connections" acquired with ``reuse=True``, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that ``acquire(reuse=True)`` always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack. + +.. tip:: + + By context, we are actually referring to the context concept in + `contextvars `_ the + new module in Python 3.7, and its partial backport `aiocontextvars + `_. Simply speaking, you may + treat a series of function calls in a chain as in the same context, even if + there is an ``await``. It's something like a thread local in asyncio. + +:class:`~gino.engine.GinoConnection` (2) may be created through +``acquire(reuse=True)`` too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection. + +Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +:class:`~gino.engine.GinoConnection` can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more. + +lazy +"""" + +As you may have found, :class:`~gino.engine.GinoConnection` (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set ``lazy=True`` on acquire. + +A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, :class:`~gino.engine.GinoConnection` (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + +On implementation level, ``lazy`` is extremely easy in +:meth:`~gino.engine.GinoEngine.acquire`: if ``lazy=False`` then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, :class:`~gino.egnine.GinoConnection` will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all :class:`~gino.engine.GinoConnection` +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + await conn.release(permanent=False) # release (8) + await asyncio.sleep(10) # simulate long I/O work + await conn.scalar('select now()') # re-acquire a new raw connection, + # not necessarily the same (8) + +When used together with ``reuse``, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set ``lazy=False`` on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again: + +.. image:: ../images/connection.png + :align: center + + +reusable +"""""""" + +Usually, you don't have to worry about the two options ``reuse`` and ``lazy``, +using the default :meth:`~gino.engine.GinoEngine.acquire` will always create +a concrete :class:`~gino.engine.GinoConnection` with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use ``reusable=False`` on acquire. As shown in the diagram, the unreusable +:class:`~gino.engine.GinoConnection` is an orphan away from any stack:: + + async with engine.acquire(): # (2) + async with engine.acquire(reusable=False): # the unreusable connection + async with engine.acquire(reuse=True): # (3) + +Unreusable connections can be lazy. But it is usually meaningless to specify +both ``reuse=True`` and ``reusable=False`` at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +``acquire(reuse=True, reusable=False)`` unless you know what it does. + + +current_connection +"""""""""""""""""" + +Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +:attr:`~gino.engine.GinoEngine.current_connection` which is always the reusable +:class:`~gino.engine.GinoConnection` at the top of current stack, or ``None`` +if current stack is empty. + +.. tip:: + + The different between :attr:`~gino.engine.GinoEngine.current_connection` + and :meth:`acquire(reuse=True) ` is, the + latter always produces a :class:`~gino.engine.GinoConnection`, while the + former may not. + + +Executing Queries +----------------- + +Once you have a :class:`~gino.engine.GinoConnection` instance, you can start +executing queries with it. There are 6 variants of the execute method: +:meth:`~gino.engine.GinoConnection.all`, +:meth:`~gino.engine.GinoConnection.first`, +:meth:`~gino.engine.GinoConnection.one`, +:meth:`~gino.engine.GinoConnection.one_or_none`, +:meth:`~gino.engine.GinoConnection.scalar` and +:meth:`~gino.engine.GinoConnection.status`. They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results: + +* :meth:`~gino.engine.GinoConnection.all` returns all results in a + :class:`list`, which may be empty when the query has no result, empty but + still a :class:`list`. +* :meth:`~gino.engine.GinoConnection.first` returns the first result directly, + or ``None`` if there is no result at all. There is usually some optimization + behind the scene to efficiently get only the first result, instead of loading + the full result set into memory. +* :meth:`~gino.engine.GinoConnection.one` returns exactly one result. If there + is no result at all or if there are multiple results, an exception is raised. +* :meth:`~gino.engine.GinoConnection.one_or_none` is similar to + :meth:`~gino.engine.GinoConnection.one`, but it returns ``None`` if there is + no result instead or raising an exception. +* :meth:`~gino.engine.GinoConnection.scalar` is similar to + :meth:`~gino.engine.GinoConnection.first`, it returns the first value of the + first result. Quite convenient to just retrieve a scalar value from database, + like ``NOW()``, ``MAX()``, ``COUNT()`` or whatever generates a single value. + ``None`` is also returned when there is no result, it is up to you how to + distinguish no result and the first value is ``NULL``. +* :meth:`~gino.engine.GinoConnection.status` executes the query and discard all + the query results at all. Instead it returns the execution status line as it + is, usually a textual string. Note, there may be no optimization to only + return the status without loading the results, so make your query generate + nothing if you don't want any result. + +By "result", I meant :class:`~sqlalchemy.engine.RowProxy` of SQLAlchemy - an +immutable row instance with both :class:`tuple` and :class:`dict` interfaces. +Database values are translated twice before they are eventually stored in a +:class:`~sqlalchemy.engine.RowProxy`: first by the database driver (dialect) +from network payload to Python objects (see `Type Conversion +`_ of +how asyncpg does this), second by SQLAlchemy +:meth:`~sqlalchemy.types.TypeEngine.result_processor` depending on the actual +type and dialect. + +The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy :meth:`~sqlalchemy.engine.Connection.execute` (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +``multiparams``, all 4 methods will always return ``None`` discarding all +results. Likewise, the parameter values are processed twice too: first by +:meth:`~sqlalchemy.types.TypeEngine.bind_processor` then the database driver. + +GINO also supports SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execution_options` provided either on +:meth:`engine level `, +:meth:`connection level ` or on +:meth:`queries `. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling ``return_model`` and providing a ``model`` will make +:meth:`~gino.engine.GinoConnection.all` and +:meth:`~gino.engine.GinoConnection.first` return ORM model instance(s) instead +of :class:`~sqlalchemy.engine.RowProxy` instance(s). See also +:meth:`~sqlalchemy.engine.Connection.execution_options` for more information. + +In addition, GINO has an :meth:`~gino.engine.GinoConnection.iterate` method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a `server-side cursor +`_. + + +Implicit Execution +------------------ + +Acquire a :class:`~gino.engine.GinoConnection` and execute queries on it, that +is the most explicit way. You can also execute queries on a +:class:`~gino.engine.GinoEngine` instance. In this case, a connection will be +acquired with ``reuse=True`` for you implicitly, and released after returning:: + + await engine.scalar('select now()') + +Equals to:: + + async with engine.acquire(reuse=True) as conn: + await conn.scalar('select now()') + +This allows you to easily write connectionless code. For example:: + + async def get_now(): + return await engine.scalar('select now()') + + async def main(): + async with engine.acquire(): + now = await get_now() + await engine.status('UPDATE ...') + +In this example, ``main()`` will take only one raw connection. ``get_now()`` +can also work alone out of any ``acquire()`` context, thanks to ``reuse``. + +Furthermore, GINO provides the same query APIs on :class:`~gino.api.Gino` +directly. They are simply delegates to corresponding API methods on the +``bind``. This allows even engine-less programming:: + + db = gino.Gino() + + async def get_now(): + return await db.scalar('select now()') + + async def main(): + async with db.with_bind('postgresql://...'): + now = await get_now() + await db.status('UPDATE ...') + +.. note:: + + In this example we didn't put the two queries in an ``acquire()`` block, so + they might be executed in two different connections. + +At last, the SQLAlchemy `implicit execution +`_ +on queries also work in GINO, under an extension named ``gino``:: + + await users_table.select().gino.all() + +By default, the extension :class:`~gino.api.GinoExecutor` is injected on +:class:`~sqlalchemy.sql.expression.Executable` as a property of name ``gino`` +at the creation of :class:`~gino.api.Gino` instance. Therefore, any +:class:`~sqlalchemy.sql.expression.Executable` object has the ``gino`` +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the ``bind`` of the ``db`` instance. diff --git a/docs/zh/1.1b2/_sources/explanation/sa20.rst.txt b/docs/zh/1.1b2/_sources/explanation/sa20.rst.txt new file mode 100644 index 0000000..0c18c1f --- /dev/null +++ b/docs/zh/1.1b2/_sources/explanation/sa20.rst.txt @@ -0,0 +1,691 @@ +SQLAlchemy 2.0 +============== + +This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes. + +`SQLAlchemy 2.0 `__ will +deliver many breaking API changes, and `SQLAlchemy 1.4 +`__ will be the "interim" +version for people to eventually upgrade their software to use SQLAlchemy 2.0. + ++-------+------------+----------+----------------------------+ +| GINO | SQLAlchemy | Dialect | Comments | ++=======+============+==========+============================+ +| 1.0.x | 1.3.x | Custom | Current (old-)stable. | ++-------+------------+----------+----------------------------+ +| 1.1.x | 1.3.x | Custom | Next old-stable. | ++-------+------------+----------+----------------------------+ +| 1.2.x | 1.3.x | Custom | Future old-stable (maybe). | ++-------+------------+----------+----------------------------+ +| 1.4.x | 1.4.x | Upstream | 2.0 Interim. | ++-------+------------+----------+----------------------------+ +| 2.0.x | 2.0.x | Upstream | Future stable. | ++-------+------------+----------+----------------------------+ +| 2.1.x | 2.0.x | Upstream | Future stable iterations. | ++-------+------------+----------+----------------------------+ + +To make things easier, GINO will (luckily) also `follow the same versions +`__ for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only. + +At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won't match SQLAlchemy versions. + + +The Async Solution +------------------ + +Among all the exciting updates in SQLAlchemy 1.4 / 2.0, `native async support +`__ is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet_ to mix asynchronous stuff into current code base, avoiding making everything +async. + +Let's say we have an asynchronous method to create an asyncpg connection:: + + import asyncpg + + async def connect(): + return await asyncpg.connect("postgresql:///") + +And an end-user method to use it:: + + async def main(): + conn = await connect() + now = await conn.fetchval("SELECT now()") + +Now instead of directly calling ``connect()`` from ``main()``, I would like to add some +additional logic - let's say, a sanity check:: + + async def safe_connect(): + conn = await connect() + try: + await conn.execute("SELECT 1") + except Exception: + return None + else: + return conn + +Then the end-user should modify ``main()`` to: + +.. code-block:: python + :emphasize-lines: 2,3 + + async def main(): + conn = await safe_connect() + if conn: + now = await conn.fetchval("SELECT now()") + +OK, everything works so far, as they are all regular async code. Here's the interesting +part: ``safe_connect()`` must not be an ``async def`` method. With SQLAlchemy 1.4+, we +could: + +.. code-block:: python + :emphasize-lines: 1,3,4,6,13 + + from sqlalchemy.util import await_only, greenlet_spawn + + def sync_safe_connect(): + conn = await_only(connect()) + try: + await_only(conn.execute("SELECT 1")) + except Exception: + return None + else: + return conn + + async def safe_connect(): + return await greenlet_spawn(sync_safe_connect) + +Behind the scene, ``greenlet_spawn()`` runs the given "sync" method in a greenlet, which +uses ``await_only()`` to switch to the event loop and bridge the underlying async +methods. As ``sync_safe_connect()`` is just a normal Python method, you can imagine how +it works together with lots of other "sync" code asynchronously. + +We're not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its "sync" code base, and still being able to provide async +APIs on top of them. + + +Async SQLAlchemy +---------------- + +Although greenlet_ might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move. + +The sync library existed for years, with many assumptions like using ``threading.Lock`` +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. ``asyncio.Lock``. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues. + +As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, ``threading.Lock.acquire()`` actually works fine in a single coroutine, but `2 +concurrent coroutines `__ +acquiring the same ``threading.Lock`` may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread. + +Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base. + +However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that's GINO's part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won't work +because there is no place for ``await`` in accessing attributes (GINO doesn't like such +implicitness anyways, so yeah). + +To quickly get a picture of async SQLAlchemy (Core), here's a sample from SQLAlchemy:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + + async with engine.begin() as conn: + await conn.run_sync(meta.drop_all) + await conn.run_sync(meta.create_all) + + await conn.execute( + t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}] + ) + + async with engine.connect() as conn: + + # select a Result, which will be delivered with buffered + # results + result = await conn.execute(select(t1).where(t1.c.name == "some name 1")) + + print(result.fetchall()) + + + asyncio.run(async_main()) + + +Auto-Commit Complication +------------------------ + +After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that ``BEGIN`` starts a transaction and +``COMMIT`` / ``ROLLBACK`` ends it. But what is happening to SQL statements that is not +wrapped in ``BEGIN ... COMMIT`` blocks? + + If you do not issue a ``BEGIN`` command, then each individual statement has an + implicit ``BEGIN`` and (if successful) ``COMMIT`` wrapped around it. + + -- PostgreSQL Documentation, `3.4. Transactions + `__ + +And yes, implicit ``ROLLBACK`` if not successful. This is not directly named as an +"auto-commit" feature, but PostgreSQL does enforce it. Now imagine a database whose +"auto-commit" feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed. + +`PEP 249 `__ (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only ``commit()`` and ``rollback()`` on a +connection, but no ``begin()``. So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call ``commit()`` to persist your changes. Closing a connection will +cause pending transactions rolled back automatically. + + Note that if the database supports an auto-commit feature, this (*the auto-commit + feature -- GINO comments*) must be initially off. + + -- PEP 249 + +As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +"auto-commit" has to mimic such behavior. For example, psycopg2_ will automatically emit +a ``BEGIN`` to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen ``IDLE IN TRANSACTION``?), sometimes even +holding database locks and eventually causing a deadlock storm. + +To work around this workaround, PEP 249 does say: + + An interface method may be provided to turn it (the auto-commit feature) back on. + +So for psycopg2_, one could do this:: + + import psycopg2 + + conn = psycopg2.connect("postgresql:///") + conn.autocommit = True + conn.cursor().execute("SELECT now()") + +Now the database correctly receives this ``SELECT`` statement only, without any implicit +``BEGIN`` surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:: + + conn.cursor().execute("BEGIN") + conn.cursor().execute("UPDATE ...") + conn.cursor().execute("COMMIT") + +Or 2) turn auto-commit off again:: + + conn.autocommit = False + conn.cursor().execute("UPDATE ...") + conn.commit() + +I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg_ does provide a cleaner API, by not complying to PEP 249:: + + import asyncpg + + async def main(): + conn = await asyncpg.connect("postgresql://") + + print(await conn.fetchval("SELECT now()")) # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.execute("UPDATE ...") # UPDATE ...; + # COMMIT; + +It's much cleaner to see what's actually happening on the wire to the database. This is +also how GINO works. + + +SQLAlchemy for DB-API +--------------------- + +Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:: + + import sqlalchemy as sa + + e = sa.create_engine("postgresql:///", future=True) + with e.connect() as conn: + conn.scalar(sa.text("SELECT now()")) + +Only ``SELECT now()``? No. Here's the answer:: + + with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + conn.scalar(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +.. note:: + + We are using SQLAlchemy 2.0 API for simplification, by setting ``future=True`` using + SQLAlchemy 1.4. It's way more complicated in SQLAlchemy 1.3 and I don't want to get + into that. + +The reason behind this is, connections have "auto-commit" turned off by default. If +there is no transaction, an implicit ``BEGIN`` will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg_ +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg_ and +simulated a compatible DB-API. Like this:: + + import sqlalchemy as sa + from sqlalchemy.ext.asyncio import create_async_engine + + async def main(): + e = create_async_engine("postgresql+asyncpg:///") + async with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +If you want to modify the database permanently, you have to ``commit()`` the implicit +transaction explicitly: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("UPDATE ...")) # BEGIN; UPDATE ...; + await conn.commit() # COMMIT; + +Or use the explicit transaction API: + +.. code-block:: python + :emphasize-lines: 4,6 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Please note that, SQLAlchemy 2.0 doesn't allow soft-nested transactions. In other words, +you cannot nest 2 ``async with conn.begin():`` blocks like this: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + async with conn.begin(): # Error: a transaction is already begun + ... + +This limitation applies to implicit transactions too, even though it's weird: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + await conn.rollback() # ROLLBACK; + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Similar to Core, SQLAlchemy ORM `follows the same principal +`__. Grab a session, use +it without ``begin()``, and when you want to commit, ``commit()``. Or, use an explicit +transaction in a ``with session.begin():`` block. Personally I don't like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more. + + +SQLAlchemy AUTOCOMMIT +--------------------- + +I know you already miss the WYSIWYG asyncpg_ and GINO API. Hang in there, let's build +GINO 1.4 together with the `SQLAlchemy AUTOCOMMIT feature +`__. + +To turn AUTOCOMMIT back on, we need to set the ``isolation_level`` to ``AUTOCOMMIT`` in +``execution_options``: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Hooray! No more implicit ``BEGIN`` magic. We're one step closer. + +.. note:: + + There is also a keyword argument: + + .. code-block:: python + :emphasize-lines: 3 + + e = create_async_engine( + "postgresql+asyncpg:///", + isolation_level="AUTOCOMMIT", + ) + + But this is implemented very differently than ``execution_options``, and I don't + think it's working for GINO's use case. + +The next question is, how do we explicitly start a transaction? Let's try ``begin()``: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +Wait a second ... we've seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we're in AUTOCOMMIT mode? Even +though the ``isolation_level`` tell the driver not to send ``BEGIN`` to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction: + +.. code-block:: python + :emphasize-lines: 5,6 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.begin(): # no-op + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + +Well, not quite what we expected. With AUTOCOMMIT set, all of ``begin()``, ``commit()`` +and ``rollback()`` become no-ops. + +Similar to the answers in psycopg2_, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let's try 2): + +.. code-block:: python + :emphasize-lines: 6-8 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +It's working! According to SQLAlchemy docs, ``execution_options()`` creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well... + +.. code-block:: python + :emphasize-lines: 11,12 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same "DB-API" connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting ``isolation_level`` +modifies the value on "DB-API" connection. + +Returning a SQLAlchemy connection back to the pool resets the ``isolation_level`` to its +default value, and acquiring the same connection again will initialize the +``isolation_level`` with values from ``execution_options`` of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +``isolation_level`` again: + +.. code-block:: python + :emphasize-lines: 11 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + conn.execution_options(isolation_level="AUTOCOMMIT") + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Eventually we made it! 🎉 + +Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:: + + import gino + + async def main(): + engine = await gino.create_engine("postgresql:///") + async with engine.acquire() as conn: + await conn.scalar("SELECT now()") # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.status("UPDATE ...") # UPDATE ...; + # COMMIT; + +.. hint:: + + Now I feel that "implementing" auto-commit feature is more like restoring to the + original database behavior, and having auto-commit turned off by default should be + considered as a new feature called "auto-begin" or "implicit transaction". And it's + a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem. + + +Isolation Levels +---------------- + +By far, we only used 2 ``isolation_level`` values: + +* ``AUTOCOMMIT`` +* ``READ COMMITTED`` + +``AUTOCOMMIT`` is not a valid PostgreSQL isolation level. It's only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the "auto-begin" simulation. + +``READ COMMITTED`` is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction: + +.. code-block:: plpgsql + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +To start a transaction in a different isolation level, you may: + +.. code-block:: plpgsql + :emphasize-lines: 1,7 + + # BEGIN ISOLATION LEVEL SERIALIZABLE; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + +As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit ``BEGIN`` in place, an implicit transaction is used. So this SQL also works +individually: + +.. code-block:: plpgsql + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + +But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels: + +.. code-block:: plpgsql + :emphasize-lines: 1,7,10,16,22,28 + + # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE; + SET + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + + # BEGIN ISOLATION LEVEL READ COMMITTED; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +Then let's see how SQLAlchemy with asyncpg solves this problem: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "SERIALIZABLE"}, + ) + async with e.connect() as conn: + async with conn.begin(): # BEGIN ISOLATION LEVEL SERIALIZABLE; + await conn.execute(sa.text("UPDATE ...")) # UPDATE ...; + # COMMIT; + +Under the neath, SQLAlchemy is leveraging asyncpg's +``Connection.transaction(isolation="...")`` to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions. + +But there are 2 issues: + +* User-defined isolation level is not applied in PostgreSQL implicit transactions + (a.k.a. auto-commit statements), because no one ``SET SESSION``. +* asyncpg has a bug that ``Connection.transaction(isolation="read_committed")`` always + emit ``BEGIN`` without explicit isolation level, regardless of the actual default + isolation level. + +The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL: + +.. code-block:: python + :emphasize-lines: 13-20,23,24 + + import sqlalchemy as sa + from sqlalchemy import event + from sqlalchemy.dialects.postgresql.base import PGDialect + from sqlalchemy.ext.asyncio import create_async_engine + + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + + def set_isolation_level(dbapi_conn, record): + PGDialect.set_isolation_level( + e.sync_engine.dialect, + dbapi_conn, + "SERIALIZABLE", + ) + + event.listen(e.sync_engine, "connect", set_isolation_level) + + async with e.connect() as conn: + print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL"))) + # Outputs: serializable + + +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ +.. _psycopg2: https://www.psycopg.org/docs/ +.. _asyncpg: https://github.com/MagicStack/asyncpg diff --git a/docs/zh/1.1b2/_sources/explanation/why.rst.txt b/docs/zh/1.1b2/_sources/explanation/why.rst.txt new file mode 100644 index 0000000..77455b5 --- /dev/null +++ b/docs/zh/1.1b2/_sources/explanation/why.rst.txt @@ -0,0 +1,244 @@ +Why Asynchronous ORM? +===================== + +Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly. + + +When asyncio Helps +------------------ + +As Mike Bayer - the author of SQLAlchemy - `pointed out +`__, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM". + +The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +:doc:`async`. So the ultimate reason to use asyncio should be the application itself, +not the database. + +For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead. + +.. image:: ../images/263px-Minimum-Tonne.svg.png + :align: right + +Another example is authentication using `OpenID Connect Authorization Code Flow +`__ as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +`Liebig's law `__, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave. + +Arbitrary delays may happen in the database too. In PostgreSQL, you can |LISTEN|_ to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case. + +In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is: + +.. |LISTEN| replace:: ``LISTEN`` +.. _LISTEN: https://www.postgresql.org/docs/current/sql-listen.html + + +How to Access Database +---------------------- + +The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here. + +Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:: + + import asyncio + from fastapi import FastAPI + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + app = FastAPI() + e = create_engine("postgresql://localhost") + Session = sessionmaker(bind=e) + + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +This follows advices found in :ref:`session_faq_whentocreate` from SQLAlchemy - linking +the **session scope** to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is ``kill -9``. + +What just happened is called a `resource starvation +`__. While the first 10 +requests were waiting for the async work (``sleep(0.1)``), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default ``max_overflow=10``) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition. + +The root cause of such starvation is making asynchronous calls in a **transaction +scope** created implicitly by the default ``autocommit=False``. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in **transaction scopes** by explicitly ending them:: + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + session.rollback() # return the connection to avoid starvation + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +Because of the implicit nature of typical ORMs, it's very hard to identify all such +**transaction scopes** and make sure there's no ``await`` in them - you may have started +an implicit transaction by simply accessing a property like ``current_user.name``. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this. + +What about thread pool? + +The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool. + +This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery_. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design. + +In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs. + +.. _Celery: http://www.celeryproject.org/ + + +Asynchronous ORM Done Right +--------------------------- + +I would describe a proper asynchronous ORM as follows: + + +Don't Starve. +^^^^^^^^^^^^^ + +The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything ``async``. + +In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:: + + @app.get("/") + async def read_root(): + async with db.acquire() as conn: # } + async with conn.begin(): # } These two won't block the main thread + now = await db.scalar("SELECT now()") + await asyncio.sleep(0.1) # do some async work + return str(now) + +Even though this code won't cause any resource starvation, using ``await`` within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages: + +1. As the database connection pool is usually much smaller than the asynchronous server + concurrency, this kind of code caps the concurrency down to the DB pool level. +2. Taking a database connection from the pool for nothing is a waste of resource, + especially when the database pool is the shortest stave. +3. Database transactions should be kept short as much as possible. Because long-hanging + transactions may keep certain database locks, leading to performance issues or even + triggering a chain reaction of deadlocks. + + +Be explicit. +^^^^^^^^^^^^ + +With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an ``await`` or ``async with``, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following `the pattern of Twisted +`__, asyncio is already forcing +explicit ``await`` for any asynchronous operations. + +Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example: + +* We could design the ORM model instances to be stateless, so that the users don't have + to learn and worry about maintaining the state of the instances. +* There shouldn't be any "buffered operations" which users could "flush" with a single + statement once for all. +* The user should just give direct one-off commands and the ORM executes them right away. +* Also I think the convenience tooling should be well-balanced, the user doesn't have to + guess or remember what an API means - for example, there're more than one ways to load + a many-to-one relationship, I'd prefer to write the query by myself rather than trying + to remember what "join_without_n_plus_1()" means. + + +Be productive. +^^^^^^^^^^^^^^ + +Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two? + +I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design. + +Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again. diff --git a/docs/zh/1.1b2/_sources/how-to.rst.txt b/docs/zh/1.1b2/_sources/how-to.rst.txt new file mode 100644 index 0000000..00a13df --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to.rst.txt @@ -0,0 +1,7 @@ +How-to Guides +============= + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/zh/1.1b2/_sources/how-to/alembic.rst.txt b/docs/zh/1.1b2/_sources/how-to/alembic.rst.txt new file mode 100644 index 0000000..aa60714 --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/alembic.rst.txt @@ -0,0 +1,135 @@ +Use Alembic +=========== + +Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO. + +To add migrations to project first of all, add alembic as dependency: + +.. code-block:: console + + $ pip install --user alembic + +When you need to set up alembic for your project. + +Prepare sample project. We will have a structure: + + +.. code-block:: console + + alembic_sample/ + my_app/ + models.py + +Inside ``models.py`` define simple DB Model with GINO: + +.. code-block:: python + + from gino import Gino + + db = Gino() + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + +Set up Alembic +^^^^^^^^^^^^^^ + +This will need to be done only once. Go to the main folder of your project +``alembic_sample`` and run: + +.. code-block:: console + + $ alembic init alembic + + +Alembic will create a bunch of files and folders in your project directory. One of them +will be ``alembic.ini``. Open ``alembic.ini`` (you can find it in the main project +folder ``alembic_sample``). Now change property ``sqlalchemy.url =`` with your DB +credentials. Like this: + +.. code-block:: ini + + sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}} + + +Next go to folder ``alembic/`` and open ``env.py`` file. Inside the ``env.py`` file you +need to import the ``db`` object. In our case ``db`` object is ``db`` from ``models`` +modules. This is a variable that links to your ``Gino()`` instance. + +Inside ``alembic/env.py``:: + + from main_app.models import db + + +And change ``target_metadata =`` to:: + + target_metadata = db + +That’s it. We finished setting up Alembic for a project. + +.. note:: + + All ``alembic`` commands must be run always from the folder that contains the + ``alembic.ini`` file. + + +Create first migration revision +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema. + +.. code-block:: console + + $ alembic revision -m "first migration" --autogenerate --head head + +If you have any problems relative to package imports similar to this example: + +.. code-block:: console + + File "alembic/env.py", line 7, in + from main_app.models import db + ModuleNotFoundError: No module named 'main_app' + +Either install your project locally with ``pip install -e .``, ``poetry install`` or +``python setup.py develop``, or add you package to PYTHONPATH, like this: + +.. code-block:: console + + $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample + +After the successful run of ``alembic revision`` in folder ``alembic/versions`` you will +see a file with new migration. + + +Apply migration on DB +^^^^^^^^^^^^^^^^^^^^^ + +Now time to apply migration to DB. It will create tables based on you DB Models. + +.. code-block:: console + + $ alembic upgrade head + +Great. Now you apply your first migration. Congratulations! + +Next time, when you will make any changes in DB models just do: + +.. code-block:: console + + $ alembic revision -m "your migration description" --autogenerate --head head + +And + +.. code-block:: console + + alembic upgrade head + + +Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org diff --git a/docs/zh/1.1b2/_sources/how-to/bakery.rst.txt b/docs/zh/1.1b2/_sources/how-to/bakery.rst.txt new file mode 100644 index 0000000..2f30075 --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/bakery.rst.txt @@ -0,0 +1,214 @@ +Bake Queries +============ + +.. versionadded:: 1.1 + +Baked queries are used to boost execution performance for constantly-used queries. +Similar to the :doc:`orm/extensions/baked` in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to **bake them before creating the engine**. + +GINO provides two approaches for baked queries: + +1. Low-level :class:`~gino.bakery.Bakery` API +2. High-level :meth:`Gino.bake() ` integration + + +Use Bakery with Bare Engine +--------------------------- + +First, we need a bakery:: + + import gino + + bakery = gino.Bakery() + +Then, let's bake some queries:: + + db_time = bakery.bake("SELECT now()") + +Or queries with parameters:: + + user_query = bakery.bake("SELECT * FROM users WHERE id = :uid") + +Let's assume we have this ``users`` table defined in SQLAlchemy Core:: + + import sqlalchemy as sa + + metadata = sa.MetaData() + user_table = sa.Table( + "users", metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("name", sa.String), + ) + +Now we can bake a similar query with SQLAlchemy Core:: + + user_query = bakery.bake( + sa.select([user_table]).where(user.c.id == sa.bindparam("uid")) + ) + +These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:: + + engine = await gino.create_engine("postgresql://localhost/", bakery=bakery) + +By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene. + +To execute the baked queries, you could treat the :class:`~gino.bakery.BakedQuery` +instances as if they are the queries themselves, for example:: + + now = await engine.scalar(db_time) + +Pass in parameter values:: + + row = await engine.first(user_query, uid=123) + + +Use the :class:`~gino.api.Gino` Integration +-------------------------------------------- + +In a more common scenario, there will be a :class:`~gino.api.Gino` instance, which has +usually a ``bind`` set - either explicitly or by the Web framework extensions:: + + from gino import Gino + + db = Gino() + + async def main(): + async with db.with_bind("postgresql://localhost/"): + ... + +A :class:`~gino.bakery.Bakery` is automatically created in the ``db`` instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + db_time = db.bake("SELECT now()") + user_getter = db.bake(User.query.where(User.id == db.bindparam("uid"))) + +And the execution is also simplified with the same ``bind`` magic:: + + async def main(): + async with db.with_bind("postgresql://localhost/"): + print(await db_time.scalar()) + + user: User = await user_getter.first(uid=1) + print(user.name) + +To make things easier, you could even define the baked queries directly on the +model:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + @db.bake + def getter(cls): + return cls.query.where(cls.id == db.bindparam("uid")) + + @classmethod + async def get(cls, uid): + return await cls.getter.one_or_none(uid=uid) + +Here GINO treats the ``getter()`` as a :meth:`~gino.declarative.declared_attr` with +``with_table=True``, therefore it takes one positional argument ``cls`` for the ``User`` +class. + + +How to customize loaders? +------------------------- + +If possible, you could bake the additional execution options into the query:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + ) + +The :meth:`~gino.bakery.Bakery.bake` method accepts keyword arguments as execution +options to e.g. simplify the example above into:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")), + loader=User.load(comment="Added by loader."), + ) + +If the query construction is complex, :meth:`~gino.bakery.Bakery.bake` could also be +used as a decorator:: + + @db.bake + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + +Or with short execution options:: + + @db.bake(loader=User.load(comment="Added by loader.")) + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")) + +Meanwhile, it is also possible to override the loader at runtime:: + + user: User = await user_getter.load(User).first(uid=1) + print(user.name) # no more comment on user! + +.. hint:: + + This override won't affect the baked query - it's used only in this execution. + + +What APIs are available on :class:`~gino.bakery.BakedQuery`? +------------------------------------------------------------ + +:class:`~gino.bakery.BakedQuery` is a :class:`~gino.api.GinoExecutor`, so it inherited +all the APIs like :meth:`~gino.api.GinoExecutor.all`, +:meth:`~gino.api.GinoExecutor.first`, :meth:`~gino.api.GinoExecutor.one`, +:meth:`~gino.api.GinoExecutor.one_or_none`, :meth:`~gino.api.GinoExecutor.scalar`, +:meth:`~gino.api.GinoExecutor.status`, :meth:`~gino.api.GinoExecutor.load`, +:meth:`~gino.api.GinoExecutor.timeout`, etc. + +:class:`~gino.api.GinoExecutor` is actually the chained ``.gino`` helper API seen +usually in queries like this:: + + user = await User.query.where(User.id == 123).gino.first() + +So a :class:`~gino.bakery.BakedQuery` can be seen as a normal query with the ``.gino`` +suffix, plus it is directly executable. + +.. seealso:: + + Please see API document of :mod:`gino.bakery` for more information. + + +I don't want the prepared statements. +------------------------------------- + +If you don't need all the baked queries (``m``) to create prepared statements for all +the active database connections (``n``) in the beginning, you could set +``prebake=False`` in the engine initialization to prevent the default initial +``m x n`` prepare calls:: + + e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False) + +Or if you're using bind:: + + await db.set_bind("postgresql://...", prebake=False) + +This is useful when you're depending on ``db.gino.create_all()`` to create the tables, +because the prepared statements can only be created after the table creation. + +The prepared statements will then be created and cached lazily on demand. + diff --git a/docs/zh/1.1b2/_sources/how-to/contributing.rst.txt b/docs/zh/1.1b2/_sources/how-to/contributing.rst.txt new file mode 100644 index 0000000..cbab989 --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/contributing.rst.txt @@ -0,0 +1,152 @@ +.. highlight:: console + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/python-gino/gino/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `gino` for local development. + +1. Fork the `gino` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/gino.git + +3. Create a branch for local development:: + + $ cd gino/ + $ git checkout -b name-of-your-bugfix-or-feature + +Now you can make your changes locally. + +4. Create virtual environment. Example for virtualenvwrapper:: + + $ mkvirtualenv gino + +5. Activate the environment and install requirements:: + + $ pip install -r requirements_dev.txt + +6. When you're done making changes, check that your changes pass syntax checks:: + + $ flake8 gino tests + +7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):: + + $ pytest tests + $ tox + +8. For docs run:: + + $ make docs + +It will build and open up docs in your browser. + +9. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +10. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check + https://github.com/python-gino/gino/actions?query=event%3Apull_request + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test -svx tests.test_gino + +By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:: + + CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino'; + CREATE DATABASE gino WITH OWNER = gino; + +Then run the tests like so:: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino + $ py.test + +Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):: + + $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem + $ openssl rsa -in privkey.pem -passin pass:abcd -out server.key + $ openssl req -x509 -in server.req -text -key server.key -out server.crt + $ chmod 600 server.key + $ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + +Terminal 2 (client):: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433 + $ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'" + $ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;" + $ pytest tests/test_aiohttp.py diff --git a/docs/zh/1.1b2/_sources/how-to/crud.rst.txt b/docs/zh/1.1b2/_sources/how-to/crud.rst.txt new file mode 100644 index 0000000..dd23336 --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/crud.rst.txt @@ -0,0 +1,5 @@ +==== +CRUD +==== + +**THIS IS A WIP** diff --git a/docs/zh/1.1b2/_sources/how-to/faq.rst.txt b/docs/zh/1.1b2/_sources/how-to/faq.rst.txt new file mode 100644 index 0000000..73c340a --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/faq.rst.txt @@ -0,0 +1,389 @@ +Frequently Asked Questions +========================== + +SQLAlchemy 1.4 supports asyncio, what will GINO be? +--------------------------------------------------- + +Starting from 1.4, SQLAlchemy will `support asyncio +`__. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +`greenlet `__. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed. + +Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features: + +* Contextual Connections (see :doc:`../explanation/engine`) +* SQLAlchemy Core-based CRUD models +* The GINO Loader system +* Async MySQL support +* Typing support :sup:`NEW` +* `Trio `__ support :sup:`NEW` +* Execution performance :sup:`NEW` + +As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized. + + +ORM or not ORM? +--------------- + +GINO does perform the Object-Relational Mapping work under the +`Data Mapper Pattern `_, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely `non-ORM way +`__. + + +Can I use features of SQLAlchemy ORM? +------------------------------------- + +SQLAlchemy has `two parts `__: + +* SQLAlchemy Core +* SQLAlchemy ORM + +GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO. + + +How to join? +------------ + +GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know `how to write join in +SQLAlchemy `_. +Especially, `Ádám `_ made some amazing upgrades in +GINO `#113 `_ to make join easier, so +that you can use model classes directly as if they are tables in joining:: + + results = await User.join(Book).select().gino.all() + + +How to connect to database through SSL? +--------------------------------------- + +It depends on the dialect and database driver. For asyncpg, keyword arguments +on :func:`asyncpg.connect() ` are directly +available on :func:`~gino.create_engine` or :meth:`db.set_bind() +`. Therefore, enabling SSL is rather easy:: + + engine = await gino.create_engine(..., ssl=True) + + +What is aiocontextvars and what does it do? +------------------------------------------- + +It is a partial backport of the new built-in module `contextvars +`_ introduced in Python +3.7. In Python 3.5 and 3.6, ``aiocontextvars`` patches ``loop.create_task()`` +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2 + +If you are using Python 3.7, then ``aiocontextvars`` does nothing at all. + +.. note:: + + This answer is for GINO 0.8 and later, please check earlier versions of + this documentation if you are using GINO 0.7. + + +How to define relationships? +---------------------------- + +GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see :doc:`loaders` for more information. + + +How to define index with multiple columns? +------------------------------------------ + +:: + + class User(db.Model): + __tablename__ = 'users' + + first_name = db.Column(db.Unicode()) + last_name = db.Column(db.Unicode()) + + _name_idx = db.Index('index_on_name', 'first_name', 'last_name') + +The ``_name_idx`` is not used. + + +Is there a django admin interface for GINO? +------------------------------------------- + +Not quite yet, please follow `this discussion +`__. + + +How to use multiple databases for different users on the fly? +------------------------------------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. + +In order to use multiple databases, you would need multiple +:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with +lazy engine creation:: + + from asyncio import Future + from contextvars import ContextVar + + from fastapi import FastAPI, Request + from gino import create_engine + from gino.ext.starlette import Gino + + engines = {} + dbname = ContextVar("dbname") + + + class ContextualGino(Gino): + @property + def bind(self): + e = engines.get(dbname.get("")) + if e and e.done(): + return e.result() + else: + return self._bind + + @bind.setter + def bind(self, val): + self._bind = val + + + app = FastAPI() + db = ContextualGino(app) + + + @app.middleware("http") + async def lazy_engines(request: Request, call_next): + name = request.query_params.get("db", "postgres") + fut = engines.get(name) + if fut is None: + fut = engines[name] = Future() + try: + engine = await create_engine("postgresql://localhost/" + name) + except Exception as e: + fut.set_exception(e) + del engines[name] + raise + else: + fut.set_result(engine) + await fut + dbname.set(name) + return await call_next(request) + + + @app.get("/") + async def get(): + return dict(dbname=await db.scalar("SELECT current_database()")) + + +How to load complex query results? +---------------------------------- + +The API doc of :mod:`gino.loader` explains the available loaders, and there're a few +examples in :doc:`loaders` too. + +Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders - +:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Be ware of the :class:`tuple` in ``.gino.load((...))``. + + + +How to do bulk or batch insert / update? +----------------------------------------- + +For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:: + + new_names = ["Austin", "Ali", "Jeff", "Marissa"] + +To quickly insert the names in one query, first construct a dict with the +``{"model_key": "value"}`` format:: + + new_names_dict = [dict(name=new_name) for new_name in new_names] + >> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}] + +Finally, run an insert statement on the model:: + + await User.insert().gino.all(new_names_dict) + + +How to print the executed SQL? +------------------------------ + +GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``. +(Or ``db.set_bind(..., echo=True)``) Please see also `here +`__. + +If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`. + + +How to run ``EXISTS`` SQL? +-------------------------- + +:: + + await db.scalar(db.exists().where(User.email == email).select()) + + +How to work with Alembic? +------------------------- + +The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the +key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py`` +will do. See :doc:`alembic` for more details. + + +How to join the same table twice? +--------------------------------- + +This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +:func:`~gino.crud.CRUDModel.alias`, for example:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.ForeignKey("users.id")) + + Parent = User.alias() + query = User.outerjoin(Parent, User.parent_id == Parent.id).select() + users = await query.gino.load(User.load(parent=Parent)).all() + + +.. _raw-sql: + +How to execute raw SQL with parameters? +--------------------------------------- + +Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments:: + + query = db.text('SELECT * FROM users WHERE id = :id_val') + row = await db.first(query, id_val=1) + +You may even load the rows into model instances:: + + query = query.execution_options(loader=User) + user = await db.first(query, id_val=1) + + +Gino engine is not initialized? +------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before +this, you'll see this error: + +.. code-block:: text + + gino.exceptions.UninitializedError: Gino engine is not initialized. + +You could use either: + +* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the + bind on the :class:`~gino.api.Gino` instance. +* Use one of the Web framework extensions to set the bind for you in usually the server + start-up hook. +* Use explicit ``bind`` for each execution, for example:: + + engine = await create_engine("...") + # ... + user = await User.get(request.user_id, bind=engine) + + +How can I do SQL xxxx in GINO? +------------------------------ + +GINO uses `SQLAlchemy Core `__ queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column +attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column` +instances, you can use them in building your SQLAlchemy Core queries. + +Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above. diff --git a/docs/zh/1.1b2/_sources/how-to/json-props.rst.txt b/docs/zh/1.1b2/_sources/how-to/json-props.rst.txt new file mode 100644 index 0000000..45cf95d --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/json-props.rst.txt @@ -0,0 +1,157 @@ +JSON Property +============= + +GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields. + +Quick Start +----------- + +:: + + from gino import Gino + from sqlalchemy.dialects.postgresql import JSONB + + db = Gino() + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + birthday = db.DateTimeProperty() + +The ``age`` and ``birthday`` are JSON properties stored in the ``profile`` column. You +may use them the same way as a normal GINO model field:: + + u = await User.create(name="daisy", age=18) + print(u.name, u.age) # daisy 18 + +.. note:: + + ``profile`` is the default column name for all JSON properties in a model. If you + need a different column name for some JSON properties, you'll need to specify + explicitly:: + + audit_profile = db.Column(JSON, nullable=False, server_default="{}") + + access_log = db.ArrayProperty(prop_name="audit_profile") + abnormal_detected = db.BooleanProperty(prop_name="audit_profile") + +Using JSON properties in queries is supported:: + + await User.query.where(User.age > 16).gino.all() + +This is simply translated into a native JSON query like this: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16) + +Datetime type is very much the same:: + + from datetime import datetime + + await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all() + +And the generated SQL: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2 + -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0)) + +Here's a list of all the supported JSON properties: + ++----------------------------+-----------------------------+-------------+---------------+ +| JSON Property | Python type | JSON type | Database Type | ++============================+=============================+=============+===============+ +| :class:`.StringProperty` | :class:`str` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.IntegerProperty` | :class:`int` | ``number`` | ``int`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.BooleanProperty` | :class:`bool` | ``boolean`` | ``boolean`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.DateTimeProperty` | :class:`~datetime.datetime` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ObjectProperty` | :class:`dict` | ``object`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ArrayProperty` | :class:`list` | ``array`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ + + +Hooks +----- + +JSON property provides 2 instance-level hooks to customize the data:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @age.before_set + def age(self, val): + return val - 1 + + @age.after_get + def age(self, val): + return val + 1 + + u = await User.create(name="daisy", age=18) + print(u.name, u.profile, u.age) # daisy {'age': 17} 18 + +And 1 class-level hook to customize the SQLAlchemy expression of the property:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + height = db.JSONProperty() + + @height.expression + def height(cls, exp): + return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT) + + +Create Index on JSON Properties +------------------------------- + +We'll need to use :meth:`~gino.declarative.declared_attr` to wait until the model class +is initialized. The rest is very much the same as defining a usual index:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @db.declared_attr + def age_idx(cls): + return db.Index("age_idx", cls.age) + +This will lead to the SQL below executed if you run ``db.gino.create_all()``: + +.. code-block:: plpgsql + + CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER)); + +.. warning:: + + Alembic doesn't support auto-generating revisions for functional indexes yet. You'll + need to manually edit the revision. Please follow `this issue + `__ for updates. diff --git a/docs/zh/1.1b2/_sources/how-to/loaders.rst.txt b/docs/zh/1.1b2/_sources/how-to/loaders.rst.txt new file mode 100644 index 0000000..6d82937 --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/loaders.rst.txt @@ -0,0 +1,461 @@ +======================== +Loaders and Relationship +======================== + +Loaders are used to load database row results into objects. + +GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined. + + +Model Loader +------------ + +The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:: + + query = db.select([User]) + rows = await query.gino.all() + +In order to load rows into ``User`` objects, you can provide an execution +option ``loader`` with a new :class:`~gino.loader.ModelLoader` instance:: + + from gino.loader import ModelLoader + + query = db.select([User]) + query = query.execution_options(loader=ModelLoader(User)) + users = await query.gino.all() + +The :class:`~gino.loader.ModelLoader` would then load each database row into a +``User`` object. As this is frequently used, GINO made it a shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User.load()) + users = await query.gino.all() + +And another shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User) + users = await query.gino.all() + +.. tip:: + + ``User`` as ``loader`` is transformed into ``ModelLoader(User)`` by + :meth:`Loader.get() `, explained later in "Loader + Expression". + +And again:: + + query = db.select([User]) + users = await query.gino.load(User).all() + +This is identical to the normal CRUD query:: + + users = await User.query.gino.all() + + +Loader Expression +----------------- + +So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than :class:`~gino.loader.ModelLoader`, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders. + +Here is an example using all loaders at once:: + + uid, user, sep, cols = await db.select([User]).gino.load( + ( + User.id, + User, + '|', + lambda row, ctx: len(row), + ) + ).first() + +Let's check this piece by piece. Overall, the argument of +:meth:`~gino.api.GinoExecutor.load` is a tuple. This is interpreted into a +:class:`~gino.loader.TupleLoader`, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a :class:`~gino.loader.TupleLoader` is a tuple. + +:class:`~sqlalchemy.schema.Column` in Loader Expressions are interpreted as +:class:`~gino.loader.ColumnLoader`. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, :class:`~gino.loader.ColumnLoader` uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use :class:`~gino.loader.ColumnLoader`, +you'll have to declare columns for the query:: + + now = db.Column('time', db.DateTime()) + result = await db.first(db.text( + 'SELECT now() AT TIME ZONE \'UTC\'' + ).columns( + now, + ).gino.load( + ('now:', now) + ).query) + print(*result) # now: 2018-04-08 08:23:02.431847 + +Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +:class:`~gino.loader.ModelLoader`. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values. + +.. tip:: + + For a complex loader expression, the same row is given to all loaders, so + it doesn't matter ``User.id`` is already used before the model loader. + +The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +:func:`map`, the return value of the call will be the loaded result. + +At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the ``'|'`` separator, it will show up as the third item +in every result returned by the query. + + +Many-to-One Relationship +------------------------ + +A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + +So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:: + + async for child in Child.load(parent=Parent).gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +As you may have noticed, ``Child.load`` is exactly the shortcut to create +:class:`~gino.loader.ModelLoader` in the very first example. With some +additional keyword arguments, ``Child.load(parent=Parent)`` is still a +:class:`~gino.loader.ModelLoader` for ``Child``, the model loader is at the +same time a **query builder**. It is identical to do this:: + + async for child in Child.load(parent=Parent).query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +The :attr:`~gino.loader.Loader.query` dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The :class:`~gino.loader.Loader` simply forwarded unknown +attributes to its :attr:`~gino.loader.Loader.query`, that's why ``.query`` can +be omitted. + +For :class:`~gino.loader.ModelLoader`, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using :func:`setattr`. For example, ``Parent`` is +interpreted as ``ModelLoader(Parent)`` which loads ``Parent`` instances, and +``Parent`` instances are set as the ``parent`` attribute of the outer ``Child`` +instance. + +.. warning:: + + If multiple children references the same parent, then each child owns a + unique parent instance with identical values. + +.. tip:: + + You don't have to define ``parent`` attribute on ``Child``. But if you do, + you gain the ability to customize how parent is stored or retrieved. For + example, let's store the parent instance as ``_parent``:: + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + _parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + +The query builder works recursively. For :class:`~gino.loader.ModelLoader`, it +uses ``LEFT OUTER JOIN`` to connect the ``FROM`` clauses, in order to achieve +many-to-one scenario. The ``ON`` clause is determined automatically by foreign +keys. You can also customize the ``ON`` clause in case there is no foreign key +(a promise is a promise):: + + loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + async for child in loader.query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +And subloaders can be nested:: + + subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id)) + +By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values. + + +Self Referencing +---------------- + +.. warning:: + + Experimental feature. + +Self referencing is usually used to create a tree-like structure. For example:: + + class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('categories.id')) + +In order to load leaf categories with their parents, an alias is needed:: + + Parent = Category.alias() + +Then the query would be something like this:: + + parents = db.select([Category.parent_id]) + query = Category.load(parent=Parent.on( + Category.parent_id == Parent.id + )).where( + ~Category.id.in_(db.select([Category.alias().parent_id])) + ) + async for c in query.gino.iterate(): + print(f'Leaf: {c.id}, Parent: {c.parent.id}') + +The generated SQL looks like this: + +.. code-block:: SQL + + SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id + FROM categories LEFT OUTER JOIN categories AS categories_1 + ON categories.parent_id = categories_1.id + WHERE categories.id NOT IN ( + SELECT categories_2.parent_id + FROM categories AS categories_2 + ) + + +Other Relationships +------------------- + +GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + @children.setter + def add_child(self, child): + self._children.add(child) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child)).all() + +Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the ``add_child`` setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +``parent.add_child = new_child``. + +Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries. + +GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._child = None + + @property + def child(self): + return self._child + + @child.setter + def child(self, child): + self._child = child + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all() + + +Similarly, you can build many-to-many relationships in the same way:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + def add_child(self, child): + self._children.add(child) + child._parents.add(self) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._parents = set() + + @property + def parents(self): + return self._parents + + + class ParentXChild(db.Model): + __tablename__ = 'parents_x_children' + + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + child_id = db.Column(db.Integer, db.ForeignKey('children.id')) + + + query = Parent.outerjoin(ParentXChild).outerjoin(Child).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all() + +Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ``ParentXChild`` instances. + + +Advanced Usage of Loaders +------------------------- + +You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For `example +`_, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Using alias to get ID-ascending pairs from the same table:: + + ua1 = User.alias() + ua2 = User.alias() + join_query = select([ua1, ua2]).where(ua1.id < ua2.id) + loader = ua1.load('id'), ua2.load('id') + result = await join_query.gino.load(loader).all() + print(result) # e.g. [(1, 2), (1, 3), (2, 3)] + +Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future. diff --git a/docs/zh/1.1b2/_sources/how-to/pool.rst.txt b/docs/zh/1.1b2/_sources/how-to/pool.rst.txt new file mode 100644 index 0000000..4574ab1 --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/pool.rst.txt @@ -0,0 +1,27 @@ +=============== +Connection Pool +=============== + +Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +:class:`~gino.dialects.asyncpg.NullPool`), and users can define their own pools. +The base class should be :class:`~gino.dialects.base.Pool`. + +To use non-default pools in raw GINO:: + + from gino.dialects.asyncpg import NullPool + create_engine('postgresql://...', pool_class=NullPool) + +To use non-default pools in extensions (taking Sanic as an example):: + + from gino.dialects.asyncpg import NullPool + from gino.ext.sanic import Gino + + app = sanic.Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_KWARGS = dict( + pool_class=NullPool, + ) + db = Gino() + db.init_app(app) diff --git a/docs/zh/1.1b2/_sources/how-to/schema.rst.txt b/docs/zh/1.1b2/_sources/how-to/schema.rst.txt new file mode 100644 index 0000000..c57eaaa --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/schema.rst.txt @@ -0,0 +1,232 @@ +================== +Schema Declaration +================== + +There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy :class:`~sqlalchemy.schema.Table`. + + +GINO Engine +----------- + +This is the minimized way to use GINO - using only +:class:`~gino.engine.GinoEngine` (and :class:`~gino.engine.GinoConnection` +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two. + +For example, the table declaration is the same as SQLAlchemy core `tutorial +`_:: + + from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey + + metadata = MetaData() + + users = Table( + 'users', metadata, + + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + ) + + addresses = Table( + 'addresses', metadata, + + Column('id', Integer, primary_key=True), + Column('user_id', None, ForeignKey('users.id')), + Column('email_address', String, nullable=False) + ) + +.. note:: + + When using GINO Engine only, it is usually your own business to create the + tables with either :meth:`~sqlalchemy.schema.MetaData.create_all` on a + normal non-async SQLAlchemy engine, or using Alembic. However it is still + possible to be done with GINO if it had to:: + + import gino + from gino.schema import GinoSchemaVisitor + + async def main(): + engine = await gino.create_engine('postgresql://...') + await GinoSchemaVisitor(metadata).create_all(engine) + +Then, construct queries, in SQLAlchemy core too:: + + ins = users.insert().values(name='jack', fullname='Jack Jones') + +So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:: + + async def main(): + engine = await gino.create_engine('postgresql://localhost/gino') + conn = await engine.acquire() + await conn.status(ins) + print(await conn.all(users.select())) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Here :func:`~gino.create_engine` creates a :class:`~gino.engine.GinoEngine`, +then :meth:`~gino.engine.GinoEngine.acquire` checks out a +:class:`~gino.engine.GinoConnection`, and +:meth:`~gino.engine.GinoConnection.status` executes the insert and returns the +status text. This works similarly as SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execute` - they take the same parameters +but return a bit differently. There are also other similar query APIs: + +* :meth:`~gino.engine.GinoConnection.all` returns a list of + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.first` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.one` returns one + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.one_or_none` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.scalar` returns a single value, or + ``None`` +* :meth:`~gino.engine.GinoConnection.iterate` returns an asynchronous iterator + which yields :class:`~sqlalchemy.engine.RowProxy` + +Please go to their API for more information. + + +GINO Core +--------- + +In previous scenario, :class:`~gino.engine.GinoEngine` must not be set to +:attr:`metadata.bind ` because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of :class:`~sqlalchemy.schema.MetaData` as :class:`~gino.api.Gino`, +usually instantiated globally under the name of ``db``. It can be used as a +normal :class:`~sqlalchemy.schema.MetaData` still offering some conveniences: + +* It delegates most public types you can access on ``sqlalchemy`` +* It works with both normal SQLAlchemy engine and asynchronous GINO engine +* It exposes all query APIs on :class:`~gino.engine.GinoConnection` level +* It injects two ``gino`` extensions on SQLAlchemy query clauses and schema + items, allowing short inline execution like ``users.select().gino.all()`` +* It is also the entry for the third scenario, see later + +Then we can achieve previous scenario with less code like this:: + + from gino import Gino + + db = Gino() + + users = db.Table( + 'users', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('name', db.String), + db.Column('fullname', db.String), + ) + + addresses = db.Table( + 'addresses', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('user_id', None, db.ForeignKey('users.id')), + db.Column('email_address', db.String, nullable=False) + ) + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await users.insert().values( + name='jack', + fullname='Jack Jones', + ).gino.status() + print(await users.select().gino.all()) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but ``sqlalchemy`` seems +never imported. This is useful when ORM is unwanted. + +.. tip:: + + `asyncpgsa `_ does the same thing, + but in a conceptually reversed way - instead of having asyncpg work for + SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that + way too because GINO is inspired by asyncpgsa). Either way works fine, it's + just a matter of taste of whose API style to use, SQLAlchemy or asyncpg. + + +GINO ORM +-------- + +If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:: + + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + fullname = db.Column(db.String) + + + class Address(db.Model): + __tablename__ = 'addresses' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(None, db.ForeignKey('users.id')) + email_address = db.Column(db.String, nullable=False) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await User.create(name='jack', fullname='Jack Jones') + print(await User.query.gino.all()) + # Outputs: [] + +.. important:: + + The ``__tablename__`` is a mandatory field to define a concrete model. + +As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in ``db``. The ``class`` style is just +more declarative. Instead of ``users.c.name``, you can now access the column by +``User.name``. The implicitly created :class:`~sqlalchemy.schema.Table` is +available at ``User.__table__`` and ``Address.__table__``. You can use anything +that works in GINO core here. + +.. note:: + + Column names can be different as a class property and database column. + For example, name can be declared as + ``nickname = db.Column('name', db.Unicode(), default='noname')``. In this + example, ``User.nickname`` is used to access the column, while in database, + the column name is ``name``. + + What's worth mentioning is where raw SQL statements are used, or + ``TableClause`` is involved, like ``User.insert()``, the original name is + required to be used, because in this case, GINO has no knowledge about the + mappings. + +.. tip:: + + ``db.Model`` is a dynamically created parent class for your models. It is + associated with the ``db`` on initialization, therefore the table is put in + the very ``db`` when you declare your model class. + +Things become different when it comes to CRUD. You can use model level methods +to directly :meth:`~gino.crud.CRUDModel.create` a model instance, instead of +inserting a new row. Or :meth:`~gino.crud.CRUDModel.delete` a model instance +without needing to specify the where clause manually. Query returns model +instances instead of :class:`~sqlalchemy.engine.RowProxy`, and row values are +directly available as attributes on model instances. See also: +:doc:`/how-to/crud`. + +After all, :class:`~gino.engine.GinoEngine` is always in use. Next let's dig +more into it. diff --git a/docs/zh/1.1b2/_sources/how-to/transaction.rst.txt b/docs/zh/1.1b2/_sources/how-to/transaction.rst.txt new file mode 100644 index 0000000..558a5ae --- /dev/null +++ b/docs/zh/1.1b2/_sources/how-to/transaction.rst.txt @@ -0,0 +1,116 @@ +=========== +Transaction +=========== + +It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an ``await`` will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it. + + +Basic usage +----------- + +Transactions belong to :class:`~gino.engine.GinoConnection`. The most common +way to use transactions is through an ``async with`` statement:: + + async with connection.transaction() as tx: + await connection.status('INSERT INTO mytable VALUES(1, 2, 3)') + +This guarantees a transaction is opened when entering the ``async with`` block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at :attr:`~gino.transaction.GinoTransaction.raw_transaction`, but in +most cases you don't need to touch it. + +GINO provides two convenient shortcuts to end the transaction early: + +* :meth:`tx.raise_commit() ` +* :meth:`tx.raise_rollback() ` + +They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the ``async with`` block after +:meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` is skipped. The +internal exception is inherited from :exc:`BaseException` so that normal ``try +... except Exception`` block can't trap it. This exception stops propagating at +the end of ``async with`` block, so you don't need to worry about handling it. + +Transactions can also be started on a :class:`~gino.engine.GinoEngine`:: + + async with engine.transaction() as tx: + await engine.status('INSERT INTO mytable VALUES(1, 2, 3)') + +Here a :class:`~gino.engine.GinoConnection` is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The :class:`~gino.engine.GinoConnection` instance is accessible at +:attr:`tx.connection `. Other than +that, everything else is the same. + +.. important:: + + The implicit connection is by default borrowed with ``reuse=True``. That + means using :meth:`~gino.engine.GinoEngine.transaction` of + :class:`~gino.engine.GinoEngine` within a connection context is the same as + calling :meth:`~gino.engine.GinoConnection.transaction` of the current + connection without having to reference it, no separate connection shall be + created. + +Similarly, if your :class:`~gino.api.Gino` instance has a bind, you may also do +the same on it:: + + async with db.transaction() as tx: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + + +Nested Transactions +------------------- + +Transactions can be nested, nested transaction will create a `savepoint +`_ as for +now on asyncpg. A similar example from asyncpg doc:: + + async with connection.transaction() as tx1: + await connection.status('CREATE TABLE mytab (a int)') + + # Create a nested transaction: + async with connection.transaction() as tx2: + await connection.status('INSERT INTO mytab (a) VALUES (1), (2)') + # Rollback the nested transaction: + tx2.raise_rollback() + + # Because the nested transaction was rolled back, there + # will be nothing in `mytab`. + assert await connection.all('SELECT a FROM mytab') == [] + +As you can see, the :meth:`~gino.transaction.GinoTransaction.raise_rollback` +breaks only the ``async with`` block of the specified ``tx2``, the outer +transaction ``tx1`` just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate. + +Because of the default reusing behavior, transactions on engine or ``db`` +follows the same nesting rules. Please see +:class:`~gino.transactions.GinoTransaction` for more information. + + +Manual Control +-------------- + +Other than using ``async with``, you can also manually control the +transaction:: + + tx = await connection.transaction() + try: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + await tx.commit() + except Exception: + await tx.rollback() + raise + +You can't use :meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` here, similarly it is +prohibited to use :meth:`~gino.transaction.GinoTransaction.commit` and +:meth:`~gino.transaction.GinoTransaction.rollback` in an ``async with`` block. diff --git a/docs/zh/1.1b2/_sources/index.rst.txt b/docs/zh/1.1b2/_sources/index.rst.txt new file mode 100644 index 0000000..1512377 --- /dev/null +++ b/docs/zh/1.1b2/_sources/index.rst.txt @@ -0,0 +1,127 @@ +Welcome to GINO's documentation! +================================ + +.. image:: https://img.shields.io/pypi/v/gino?logo=python&logoColor=white&color=3E6CDE&style=flat-square + :alt: PyPI Release Version + :target: https://pypi.python.org/pypi/gino + +.. image:: https://img.shields.io/github/workflow/status/python-gino/gino/test?label=test&logo=github&color=3E6CDE&style=flat-square + :alt: GitHub Workflow Status for tests + :target: https://github.com/python-gino/gino/actions?query=workflow%3Atest + +.. image:: https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy&color=3E6CDE&style=flat-square + :alt: Codacy coverage + :target: https://app.codacy.com/gh/python-gino/gino/dashboard + +.. image:: https://img.shields.io/badge/Dependabot-active-brightgreen?logo=dependabot&color=3E6CDE&style=flat-square + :target: https://app.dependabot.com/accounts/python-gino/projects/129260 + :alt: Dependabot + + +GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy_ core for Python asyncio_. Now (early 2020) GINO supports only one +dialect asyncpg_. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _asyncpg: https://github.com/MagicStack/asyncpg + + +.. cssclass:: boxed-nav + +* .. image:: images/tutorials.svg + + :doc:`tutorials` + + Lessons for the newcomer to get started + +* .. image:: images/how-to.svg + + :doc:`how-to` + + Solve specific problems by steps + +* .. image:: images/explanation.svg + + :doc:`explanation` + + Explains the background and context + +* .. image:: images/reference.svg + + :doc:`reference` + + Describes the software as it is + + +Useful Links +------------ + +.. cssclass:: boxed-nav + +* .. image:: images/github.svg + + `Source Code `_ + + https://github.com/python-gino/gino + +* .. image:: images/community.svg + + `Community `_ + + https://gitter.im/python-gino/Lobby + +* .. image:: images/open-source.svg + + `BSD license `_ + + GINO is free software + +* .. image:: images/python.svg + + `Download `_ + + Download GINO from PyPI + + +.. cssclass:: divio + +Sections by `Divio `_. + +.. toctree:: + :caption: Tutorials + :maxdepth: 1 + :glob: + :hidden: + + tutorials + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* + +.. toctree:: + :caption: How-to Guides + :maxdepth: 1 + :glob: + :hidden: + + how-to + how-to/* + +.. toctree:: + :caption: Explanation + :maxdepth: 1 + :glob: + :hidden: + + explanation + explanation/* + +.. toctree:: + :caption: Reference + :maxdepth: 1 + :glob: + :hidden: + + reference + reference/* diff --git a/docs/zh/1.1b2/_sources/reference.rst.txt b/docs/zh/1.1b2/_sources/reference.rst.txt new file mode 100644 index 0000000..977912b --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference.rst.txt @@ -0,0 +1,7 @@ +Reference +========= + +.. toctree:: + :glob: + + reference/* diff --git a/docs/zh/1.1b2/_sources/reference/api.rst.txt b/docs/zh/1.1b2/_sources/reference/api.rst.txt new file mode 100644 index 0000000..23fc548 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api.rst.txt @@ -0,0 +1,6 @@ +API Reference +============= + +.. toctree:: + + api/gino diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.aiocontextvars.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.aiocontextvars.rst.txt new file mode 100644 index 0000000..4728456 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.aiocontextvars.rst.txt @@ -0,0 +1,7 @@ +gino.aiocontextvars module +========================== + +.. automodule:: gino.aiocontextvars + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.api.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.api.rst.txt new file mode 100644 index 0000000..8825a74 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.api.rst.txt @@ -0,0 +1,7 @@ +gino.api module +=============== + +.. automodule:: gino.api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.bakery.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.bakery.rst.txt new file mode 100644 index 0000000..5762ab7 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.bakery.rst.txt @@ -0,0 +1,7 @@ +gino.bakery module +================== + +.. automodule:: gino.bakery + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.crud.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.crud.rst.txt new file mode 100644 index 0000000..9683c99 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.crud.rst.txt @@ -0,0 +1,7 @@ +gino.crud module +================ + +.. automodule:: gino.crud + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.declarative.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.declarative.rst.txt new file mode 100644 index 0000000..8ec0bdd --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.declarative.rst.txt @@ -0,0 +1,7 @@ +gino.declarative module +======================= + +.. automodule:: gino.declarative + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.dialects.aiomysql.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.aiomysql.rst.txt new file mode 100644 index 0000000..338ccc5 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.aiomysql.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.aiomysql module +============================= + +.. automodule:: gino.dialects.aiomysql + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.dialects.asyncpg.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.asyncpg.rst.txt new file mode 100644 index 0000000..9423016 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.asyncpg.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.asyncpg module +============================ + +.. automodule:: gino.dialects.asyncpg + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.dialects.base.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.base.rst.txt new file mode 100644 index 0000000..c512657 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.base.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.base module +========================= + +.. automodule:: gino.dialects.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.dialects.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.rst.txt new file mode 100644 index 0000000..5366ba2 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.dialects.rst.txt @@ -0,0 +1,20 @@ +gino.dialects package +===================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects.aiomysql + gino.dialects.asyncpg + gino.dialects.base + +Module contents +--------------- + +.. automodule:: gino.dialects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.engine.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.engine.rst.txt new file mode 100644 index 0000000..0075c29 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.engine.rst.txt @@ -0,0 +1,7 @@ +gino.engine module +================== + +.. automodule:: gino.engine + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.exceptions.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.exceptions.rst.txt new file mode 100644 index 0000000..d3659da --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.exceptions.rst.txt @@ -0,0 +1,7 @@ +gino.exceptions module +====================== + +.. automodule:: gino.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.ext.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.ext.rst.txt new file mode 100644 index 0000000..fe56088 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.ext.rst.txt @@ -0,0 +1,10 @@ +gino.ext package +================ + +Module contents +--------------- + +.. automodule:: gino.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.json_support.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.json_support.rst.txt new file mode 100644 index 0000000..5a7c069 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.json_support.rst.txt @@ -0,0 +1,7 @@ +gino.json\_support module +========================= + +.. automodule:: gino.json_support + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.loader.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.loader.rst.txt new file mode 100644 index 0000000..06ce463 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.loader.rst.txt @@ -0,0 +1,7 @@ +gino.loader module +================== + +.. automodule:: gino.loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.rst.txt new file mode 100644 index 0000000..b507d15 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.rst.txt @@ -0,0 +1,38 @@ +gino package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects + gino.ext + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.aiocontextvars + gino.api + gino.bakery + gino.crud + gino.declarative + gino.engine + gino.exceptions + gino.json_support + gino.loader + gino.schema + gino.strategies + gino.transaction + +Module contents +--------------- + +.. automodule:: gino + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.schema.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.schema.rst.txt new file mode 100644 index 0000000..36fbb62 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.schema.rst.txt @@ -0,0 +1,7 @@ +gino.schema module +================== + +.. automodule:: gino.schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.strategies.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.strategies.rst.txt new file mode 100644 index 0000000..01de55b --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.strategies.rst.txt @@ -0,0 +1,7 @@ +gino.strategies module +====================== + +.. automodule:: gino.strategies + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/api/gino.transaction.rst.txt b/docs/zh/1.1b2/_sources/reference/api/gino.transaction.rst.txt new file mode 100644 index 0000000..2a28e55 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/api/gino.transaction.rst.txt @@ -0,0 +1,7 @@ +gino.transaction module +======================= + +.. automodule:: gino.transaction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/1.1b2/_sources/reference/extensions.rst.txt b/docs/zh/1.1b2/_sources/reference/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/zh/1.1b2/_sources/reference/extensions/sanic.rst.txt b/docs/zh/1.1b2/_sources/reference/extensions/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/extensions/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/zh/1.1b2/_sources/reference/extensions/starlette.rst.txt b/docs/zh/1.1b2/_sources/reference/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/zh/1.1b2/_sources/reference/extensions/tornado.rst.txt b/docs/zh/1.1b2/_sources/reference/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/zh/1.1b2/_sources/reference/history.rst.txt b/docs/zh/1.1b2/_sources/reference/history.rst.txt new file mode 100644 index 0000000..65889ae --- /dev/null +++ b/docs/zh/1.1b2/_sources/reference/history.rst.txt @@ -0,0 +1,620 @@ +======= +History +======= + +GINO 1.1 +-------- + +1.1.0 (pending) +^^^^^^^^^^^^^^^ + +* Added baked query feature (#478 #659 #667) +* Added ``Query.gino.execution_options`` shortcut (#659) +* Added ``@db.declared_attr(with_table=True)`` (#659) + + +GINO 1.0 +-------- + +Migrating to GINO 1.0 +^^^^^^^^^^^^^^^^^^^^^ + +GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras: + ++------------------------+---------------------------------+ +| Extension Module | Installation in GINO 1.0 | ++========================+=================================+ +| ``gino.ext.starlette`` | ``pip install gino[starlette]`` | ++------------------------+---------------------------------+ +| ``gino.ext.aiohttp`` | ``pip install gino[aiohttp]`` | ++------------------------+---------------------------------+ +| ``gino.ext.sanic`` | ``pip install gino[sanic]`` | ++------------------------+---------------------------------+ +| ``gino.ext.tornado`` | ``pip install gino[tornado]`` | ++------------------------+---------------------------------+ +| ``gino.ext.quart`` | ``pip install gino[quart]`` | ++------------------------+---------------------------------+ + +The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed ``gino[starlette]``:: + + from gino.ext.starlette import Gino + +GINO 1.0 switched to `Poetry `__ for package and +dependency management and started to use the +`src layout `__. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process: + +* Source files are now located under ``src`` directory. +* The src dist on PyPI does not include tests, docs and some other files due to + a limitation of Poetry. + +1.0.1 (2020-06-08) +^^^^^^^^^^^^^^^^^^ + +* Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4) +* Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672) +* Multiple JSON property fixes (#661 #662 #695) +* Fixed extension typing issue (#673 #674) +* Fixed model override behavior (#694) +* Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696) + +1.0.0 (2020-04-26) +^^^^^^^^^^^^^^^^^^ + +* Switched to Poetry for package and dependency management. +* [Breaking] Moved built-in extension modules to separate PyPI packages. +* Switched to src layout. +* Switched to black code style. +* Better documentation. +* [Breaking] ``none_as_none()`` is now always enabled. +* Added representation method for engine. +* Protected the URL instance fed to ``set_bind()`` from manipulation. +* Replaced some ``assert`` with ``AssertionError`` (#258 #655) + + +GINO 0.8 +-------- + +This is also version 1.0 release candidate. + +Migrating to GINO 0.8 +^^^^^^^^^^^^^^^^^^^^^ + +1. contextvars +"""""""""""""" + +We introduced aiocontextvars_ 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the ``enable_inherit()`` or +``disable_inherit()`` calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created **after** importing ``gino`` or ``aiocontextvars``, or the patch +won't work correctly. + +There is nothing to worry about in Python 3.7. + +2. none_as_none +""""""""""""""" + +When GINO tries to load a row with all ``NULL`` values into an instance, it +will now by default return ``None`` instead of an instance with all ``None`` +attributes. To recover the default behavior of 0.7, please specify +``none_as_none(False)`` in affected model loader. + +This is especially applicable to relationship sub-loaders - if the sub-loader +found it all ``NULL``, no instance will be set to parent instance. For +example:: + + child = await Child.load(parent=Parent).query.gino.first() + +If ``child.parent_id`` is ``NULL`` in database, then the ``child`` instance +won't be called with any ``setattr(child, 'parent', ...)`` at all. (If you need +``child.parent == None`` in this case, consider setting default value +``parent = None`` in child model.) + +Please note, it is deprecated to disable ``none_as_none``, and disabling will +be removed in GINO 1.0. + +0.8.7 (2020-04-19) +^^^^^^^^^^^^^^^^^^ + +* Improved error handling when attribute names collide (Contributed by Reskov in #637 #638) +* Fixed ``with_bind`` usability in aiohttp extension (#518) + +0.8.6 (2020-02-10) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``JSONPathType`` bind processor for asyncpg (#609) +* Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600) +* Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629) +* Added ``distinct()`` to ``Alias`` (#628) + +0.8.5 (2019-11-19) +^^^^^^^^^^^^^^^^^^ + +* Improved support for ``__tablename__`` in ``declared_attr`` (Contributed by Roald Storm in #592) + +0.8.4 (2019-11-09) +^^^^^^^^^^^^^^^^^^ + +* Better loader support for models in subqueries (#573 #585) +* Allowed ``__tablename__`` to be a ``declared_attr`` (#579 #582) +* Fixed Sanic 19.9.0 compatibility (#569) +* Added one() and one_or_none() (Contributed by Ilaï Deutel in #577) +* Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538) +* Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533) +* Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520) +* Fixed Grammar (Contributed by Simeon J Morgan in #504) + +0.8.3 (2019-06-06) +^^^^^^^^^^^^^^^^^^ + +* Fixed deprecated warnings in asyncpg dialect and aiohttp (#425) +* Customizable db attribute name in aiohttp app instance (#457) +* Added Starlette support (#486) + +0.8.2 (2019-03-07) +^^^^^^^^^^^^^^^^^^ + +* Added exception for unknown JSON properties (#406 #408) +* Supported Quart 0.7 (#411) +* Accepted kwargs for db init in extensions (#407 #427) +* Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440) +* Added NullPool (#437 #441) +* Unpinned dependency versions (#447) +* Added support for SQLAlchemy 1.3 (#433 #451) + +0.8.1 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Alias supported ``Label`` (#365) +* Docs update (#308, 4c59ad, #401 by Pascal van Kooten) +* Version requirement for SQLAlchemy is updated to ``>=1.2`` (#378 #382) +* Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) + * And all other extensions (#395) +* Supported Tornado 5 (#396, also thanks to Vladimir Goncharov) +* Fixed custom JSON/JSONB type support (#402 #403) + +(Most fixes done by Tony Wang) + +0.8.0 (2018-10-16) +^^^^^^^^^^^^^^^^^^ + +* Welcome Tony Wang to the maintenance team (#335) +* Allowed custom column names (#261 #297) +* Allowed column instance in ``model.load()`` (Contributed by Jekel in #323) +* [Breaking] Upgraded to aiocontextvars 0.2.0 (#333) +* Fixed bug that the same empty stack is shared between sub-tasks (#313 #334) +* [Breaking] Made ``none_as_none()`` the default behavior (#351) +* Bug fixes and docs update + + +GINO 0.7 +-------- + +This is also version 1.0 beta 3. + +0.7.7 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Backported fix for custom JSON/JSONB type support (#402 #403) + +0.7.6 (2018-08-26) +^^^^^^^^^^^^^^^^^^ + +* Updated library support (Contributed by Tony Wang in #275 #309) +* Added ``none_as_none()`` (#281 #282) +* Added ``ARRAY`` alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289) +* Added ``Model.lookup()`` to prevent updating whole table without primary key (#287 #288) +* Added ``DB_ECHO`` in extension options (Contributed by Mykyta Holubakha in #298) +* Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305) +* Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304) +* Fixed to raise ``UninitializedError`` if bind is ``None`` (Contributed by Tony Wang in #307 #310) + +0.7.5 (2018-07-26) +^^^^^^^^^^^^^^^^^^ + +* Added friendly error message when using abstract models by mistake (#224) +* Supported Python 3.7 (Contributed by Tony Wang in #265) +* Updated documentation +* Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280) + +0.7.4 (2018-06-10) +^^^^^^^^^^^^^^^^^^ + +* Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228) +* Added Quart support (#213) +* Fixed Tornado options parsing (#231) +* Improved coding style and test coverage + +0.7.3 (2018-05-19) +^^^^^^^^^^^^^^^^^^ + +* Fix for failing binary type (#225) + +0.7.2 (2018-05-15) +^^^^^^^^^^^^^^^^^^ + +* Added prepared statement support (#14) +* Added dsn in extension config (Contributed by Yurii Shtrikker in #215) + +0.7.1 (2018-05-03) +^^^^^^^^^^^^^^^^^^ + +* Added support for inline model constraints (Contributed by Kinware in #198) +* Added docs and tests for using SSL (#202) +* Added ``declared_attr`` (#204) +* Allowed ``ModelLoader`` passively load partial model (#216) + +0.7.0 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Added Python 3.5 support (#187) +* Added support to use ``dict`` as ident for ``Model.get`` (#192) +* Added result loader (partial relationship support) (#13) +* Added documentation on relationship and transaction (#146) + + +GINO 0.6 +-------- + +This is also version 1.0 beta 2. + +Migrating to GINO 0.6 +^^^^^^^^^^^^^^^^^^^^^ + +1. Task Local +""""""""""""" + +We created a new Python package aiocontextvars_ from previous ``local.py``. If +you made use of the task local features, you should install this package. + +Previous ``gino.enable_task_local()`` and ``gino.disable_task_local()`` are +replaced by ``aiocontextvars.enable_inherit()`` and +``aiocontextvars.disable_inherit()``. However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars_ by default offers task +local even without ``enable_inherit()``, which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars_ is installed. + +There is no ``gino.get_local()`` and ``gino.reset_local()`` relevant in +aiocontextvars_. The similar thing is ``aiocontextvars.ContextVar`` instance +through its ``get()``, ``set()`` and ``delete()`` methods. + +Previous ``gino.is_local_root()`` is now +``not aiocontextvars.Context.current().inherited``. + +2. Engine +""""""""" + +GINO 0.6 hides ``asyncpg.Pool`` behind the new SQLAlchemy-alike +``gino.GinoEngine``. Instead of doing this in 0.5:: + + async with db.create_pool('postgresql://...') as pool: + # your code here + +You should change it to this in 0.6:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +This equals to:: + + engine = await gino.create_engine('postgresql://...') + db.bind = engine + try: + # your code here + finally: + db.bind = None + await engine.close() + +Or:: + + engine = await db.set_bind('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Or even this:: + + db = await gino.Gino('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Choose whichever suits you the best. + +Obviously ``GinoEngine`` doesn't provide ``asyncpg.Pool`` methods directly any +longer, but you can get the underlying ``asyncpg.Pool`` object through +``engine.raw_pool`` property. + +``GinoPool.get_current_connection()`` is now changed to ``current_connection`` +property on ``GinoEngine`` instances to support multiple engines. + +``GinoPool.execution_option`` is gone, instead ``update_execution_options()`` +on ``GinoEngine`` instance is available. + +``GinoPool().metadata`` is gone, ``dialect`` is still available. + +``GinoPool.release()`` is removed in ``GinoEngine`` and ``Gino``, the +``release()`` method on ``GinoConnection`` object should be used instead. + +These methods exist both in 0.5 ``GinoPool`` and 0.6 ``GinoEngine``: +``close()``, ``acquire()``, ``all()``, ``first()``, ``scalar()``, ``status()``. + +3. GinoConnection +""""""""""""""""" + +Similarly, ``GinoConnection`` in 0.6 is no longer a subclass of +``asyncpg.Connection``, instead it has a ``asyncpg.Connection`` instance, +accessable through ``GinoConnection.raw_connection`` property. + +``GinoConnection.metadata`` is deleted in 0.6, while ``dialect`` remained. + +``GinoConnection.execution_options()`` is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior. + +``GinoConnection.release()`` is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +``permanent=False`` to remain its previous behavior. + +And ``all()``, ``first()``, ``scalar()``, ``status()``, ``iterate()``, +``transaction()`` remained in 0.6. + +4. Query API +"""""""""""" + +All five query APIs ``all()``, ``first()``, ``scalar()``, ``status()``, +``iterate()`` now accept the same parameters as SQLAlchemy ``execute()``, +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter ``bind`` if they did. Just use the API on the ``GinoEngine`` or +``GinoConnection`` object instead. + +5. Transaction +"""""""""""""" + +Transaction interface is rewritten. Now in 0.6, a ``GinoTransaction`` object is +provided consistently from all 3 methods:: + + async with db.transaction() as tx: + # within transaction + + async with engine.transaction() as tx: + # within transaction + + async with engine.acquire() as conn: + async with conn.transaction() as tx: + # within transaction + +And different usage with ``await``:: + + tx = await db.transaction() + try: + # within transaction + await tx.commit() + except: + await tx.rollback() + raise + +The ``GinoConnection`` object is available at ``tx.connection``, while +underlying transaction object from database driver is available at +``tx.transaction`` - for asyncpg it is an ``asyncpg.transaction.Transaction`` +object. + +0.6.6 (2018-05-18) +^^^^^^^^^^^^^^^^^^ + +* Backported a fix for failing binary type (#225) + +0.6.5 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Abandoned 0.6.4 and keep 0.6.x stable +* Backported doc for transaction + +0.6.4 (2018-04-16) +^^^^^^^^^^^^^^^^^^ + +Abandoned version, please use 0.7.0 instead. + +0.6.3 (2018-04-08) +^^^^^^^^^^^^^^^^^^ + +* Added aiohttp support +* Added support for calling ``create()`` on model instances (Contributed by Kinware in #178 #180) +* Fixed ``get()`` by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184) + +0.6.2 (2018-03-24) +^^^^^^^^^^^^^^^^^^ + +* Fixed SQLAlchemy prefetch issue (#141) +* Fixed issue that mixin class on Model not working (#174) +* Added more documentation (Thanks Olaf Conradi for reviewing) + +0.6.1 (2018-03-18) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``create`` and ``drop`` for ``Enum`` type (#160) +* A bit more documentation (#159) + +0.6.0 (2018-03-14) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] API Refactored, ``Pool`` replaced with ``Engine`` + + * New API ``Engine`` replaced asyncpg ``Pool`` (#59) + * Supported different dialects, theoretically + * Used aiocontextvars_ instead of builtin task local (#89) +* [Breaking] Fixed query API with ``multiparams`` (executemany) to return correctly (#20) +* [Breaking] The query methods no longer accept the parameter ``bind`` +* [Breaking] ``Gino`` no longer exposes ``postgresql`` types +* Added ``echo`` on engine (#142) +* Added tests to cover 80% of code +* Added ``gino`` extension on ``SchemaItem`` for ``create_all`` and so on (#76 #106) +* Added ``gino`` extension on model classes for ``create()`` or ``drop()`` +* Added ``_update_request_cls`` on ``CRUDModel`` (#147) +* Rewrote the documentation (#146) + +.. _aiocontextvars: https://github.com/fantix/aiocontextvars + + +GINO 0.5 +-------- + +This is also version 1.0 beta 1. + +0.5.8 (2018-02-14) +^^^^^^^^^^^^^^^^^^ + +* Preparing for 0.6.0 which will be a breaking release +* Fixed wrong value of ``Enum`` in creation (Contributed by Sergey Kovalev in #126) + +0.5.7 (2017-11-24) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.6. + +* Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114) +* Added ``Model.outerjoin`` + +0.5.6 (2017-11-23) +^^^^^^^^^^^^^^^^^^ + +* Changed to use unnamed statement when possible (#80 #90) +* Added more example (Contributed by Kentoseth in #109) +* Added ``Model.join`` and made ``Model`` selectable (Contributed by Ádám Barancsuk in #112 #113) + +0.5.5 (2017-10-18) +^^^^^^^^^^^^^^^^^^ + +* Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87) +* Added ability to reset local storage (#84) +* Fixed bug in JSON property update +* Added update chaining feature + +0.5.4 (2017-10-04) +^^^^^^^^^^^^^^^^^^ + +* Updated example (Contributed by Kinware in #75) +* Added ``Model.insert`` (Contributed by Neal Wang in #63) +* Fixed issue that non-lazy acquiring fails dirty (#79) + +0.5.3 (2017-09-23) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``no module named cutils`` error (Contributed by Vladimir Goncharov in #73) + +0.5.2 (2017-09-10) +^^^^^^^^^^^^^^^^^^ + +* Added missing driver name on dialect (#67) +* Fixed dialect to support native decimal type (#67) + +0.5.1 (2017-09-09) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.0. + +* Reverted the extension, back to pure Python (#60) +* Used SQLAlchemy ``RowProxy`` +* Added ``first_or_404`` +* Fixed bug that ``GinoPool`` cannot be inherited + +0.5.0 (2017-09-03) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten + + * Extracted CRUD operations + * Core operations are moved to ``dialect`` and execution context + * Removed ``guess_model``, switched to explicit execution options + * Turned ``timeout`` parameter to an execution option + * Extracted ``pool``, ``connection`` and ``api`` from ``asyncpg_delegate`` +* Added support for SQLAlchemy execution options, and a few custom options +* [Breaking] Made `Model.select` return rows by default (#39) +* Moved `get_or_404` to extensions (#38) +* Added iterator on model classes (#43) +* Added Tornado extension (Contributed by Vladimir Goncharov) +* Added `Model.to_dict` (#47) +* Added an extension module to update `asyncpg.Record` with processed results + + +Early Development Releases +-------------------------- + +Considered as alpha releases. + + +0.4.1 (2017-08-20) +^^^^^^^^^^^^^^^^^^ + +* Support ``select`` on model instance + +0.4.0 (2017-08-15) +^^^^^^^^^^^^^^^^^^ + +* Made ``get_or_404`` more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31) +* Delegated ``sqlalchemy.__all__`` (Contributed by Neal Wang in #10 #33) +* [Breaking] Rewrote JSON/JSONB support (#29) +* Added ``lazy`` parameter on ``db.acquire`` (Contributed by Binghan Li in #32) +* Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34) +* Fixed ``iterate`` API to be compatible with asyncpg (#32) +* Unified exceptions +* [Breaking] Changed ``update`` API (#29) +* Bug fixes + +0.3.0 (2017-08-07) +^^^^^^^^^^^^^^^^^^ + +* Supported ``__table_args__`` (#12) +* Introduced task local to manage connection in context (#19) +* Added ``query.gino`` extension for in-place execution +* Refreshed README (#3) +* Adopted PEP 487 (Contributed by Tony Wang in #17 #27) +* Used ``weakref`` on ``__model__`` of table and query (Contributed by Tony Wang) +* Delegated asyncpg ``timeout`` parameter (Contributed by Neal Wang in #16 #22) + +0.2.3 (2017-08-04) +^^^^^^^^^^^^^^^^^^ + +* Supported any primary key (Contributed by Tony Wang in #11) + +0.2.2 (2017-08-02) +^^^^^^^^^^^^^^^^^^ + +* Supported SQLAlchemy result processor +* Added rich support on JSON/JSONB +* Bug fixes + +0.2.1 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Added ``update`` and ``delete`` API + +0.2.0 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Changed API, no longer reuses asyncpg API + +0.1.1 (2017-07-25) +^^^^^^^^^^^^^^^^^^ + +* Added ``db.bind`` +* API changed: parameter ``conn`` renamed to optional ``bind`` +* Delegated asyncpg Pool with ``db.create_pool`` +* Internal enhancement and bug fixes + +0.1.0 (2017-07-21) +^^^^^^^^^^^^^^^^^^ + +* First release on PyPI. diff --git a/docs/zh/1.1b2/_sources/tutorials.rst.txt b/docs/zh/1.1b2/_sources/tutorials.rst.txt new file mode 100644 index 0000000..6b3bb98 --- /dev/null +++ b/docs/zh/1.1b2/_sources/tutorials.rst.txt @@ -0,0 +1,9 @@ +Tutorials +========= + +.. toctree:: + :glob: + + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* diff --git a/docs/zh/1.1b2/_sources/tutorials/announcement.rst.txt b/docs/zh/1.1b2/_sources/tutorials/announcement.rst.txt new file mode 100644 index 0000000..f159e02 --- /dev/null +++ b/docs/zh/1.1b2/_sources/tutorials/announcement.rst.txt @@ -0,0 +1,297 @@ +官宣:Python 异步编程再添一利器 +=============================== + + GINO 填补了国内外 asyncio ORM 领域的空白 + +随着 Tornado\ [1]_ 和 asyncio\ [2]_ 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。\ +在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +`GINO `__ 了解一下! + +.. image:: ../images/python-gino.webp + :align: center + + +GINO 是谁 +--------- + +GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义\ [3]_\ 手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,\ +你肯定要问一句“这是谁”。 + +ORM,即关系对象映射(Object-Relational Mapping\ [4]_\ ),是一类开发人员喜闻乐见的效率工具,\ +它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢? + +因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性\ +(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),\ +创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:\ +性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 ``current_user.name`` +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试? + +传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,\ +但应用到大型项目中却十分考验开发人员的平均水平。 + +所有这些问题如果再放进异步编程的环境里,那就是 O(n\ :sup:`2`) 的复杂度了——哦不,是 +O(2\ :sup:`n`)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。 + +所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,\ +我索性在 1.0 稳定版发布的前夕做个年终总结。 + + +先说“方便快捷” +-------------- + + “方便快捷”主要说的是开发效率。 + +重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,\ +开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。 + +:: + + from gino import Gino + db = Gino() + class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + +这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟? + +没有错,这就是 SQLAlchemy ORM\ [5]_ 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core\ [6]_\ (SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道\ +(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic\ [7]_\ 、各种 SQLAlchemy 的增强插件\ [8]_\ 、专业领域的 PostGIS/geoalchemy\ [9]_\ +等,GINO 全都兼容。 + +是不是十分方便、十分快捷?不止这样。 + +GINO 一站式地解决了常用 CRUD 快捷方式\ [10]_\ 、上下文管理(aiocontextvars\ [11]_\ [12]_\ )、 +数据库事务封装和嵌套\ [13]_\ 、连接池管理和懒加载\ [14]_\ 等多项便捷功能,无额外依赖关系,即装即用。 + +:: + + daisy = await User.create(name="daisy") + await daisy.update(name="Daisy").apply() + +GINO 还提供了\ [15]_\ 各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado\ [1]_\ 、\ +aiohttp\ [16]_\ 、Sanic\ [17]_\ 、FastAPI\ [18]_\ /Starlette\ [19]_\ 、Quart\ [20]_\ +什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。 + +为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa\ [21]_ 的降维打击: + +1. 最少侵入型\ [22]_\ :SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。 +2. 终身不婚型\ [23]_\ :天生厌恶“对象”,只愿定义“表”,空手接 SQL。 +3. 火力全开型\ [24]_\ :最大程度的便利,非典型异步 ORM。 + +最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、\ +一秒可读百万行的 asyncpg\ [25]_\ ,以及 uvloop\ [26]_\ (可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,\ +深受俄罗斯和乌克兰人民的爱戴。 + + +再说“简单明了” +-------------- + + Explicit is better than implicit. + + Simple is better than complex. + + –The Zen of Python, PEP 20\ [27]_ + +Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,\ +因此 GINO 的很多设计都受到了明确性的影响。 + +比如说,GINO 的 ``Model`` 是完全无状态的普通 Python 对象(POPO\ [28]_\ —— 例如前面的 ``User`` +类,它的实例 ``daisy`` 就是内存里面的常规对象,你可以用 ``daisy.name`` 访问属性,也可以用 +``daisy.name = "DAISY"`` 来修改属性,或者用 ``u = User()`` 来创建新的实例,\ +这些操作都不会访问数据库,绝对绿色环保无毒副作用。 + +等到需要操作数据库的时候,你一定会有感知的。比如执行 ``INSERT`` 要用 +``u = await User.create()``\ ,而 ``UPDATE`` 则是 +``await u.update(name="Daisy").apply()``\ 。 + +.. hint:: + + 其中,``u.update(name="Daisy")`` 与 ``u.name = "Daisy"`` 类似,\ + 都是只在内存里修改对象的属性,不同的是 ``u.update()`` 还会返回一个包含本次变更的中间结果,\ + 对其执行 ``await xxx.apply()`` 则会将这些变更应用到数据库里。 + +这里的 ``await`` 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 ``await`` +就没有数据库操作。 + +另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders\ [29]_\ 。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 ``u = await User.get(1)`` 可以直接获取到 ID 为 ``1`` 的用户对象。\ +但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。\ +加载器的用法也是很简单的,比如一个用户可能写了很多本书: + +:: + + class Book(db.Model): + __tablename__ = "books" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + author_id = db.ForeignKey("users.id") + +然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者: + +:: + + query = Book.outerjoin(User).select() + loader = Book.load(author=User) + async for book in query.gino.load(loader).iterate(): + print(book.title, "written by", book.author.name) + +很简单的一个外连接查询 ``Book.outerjoin(User)``\ ,配合一个直观的加载器 +``Book.load(author=User)``\ ,就实现了: + +1. 执行 ``SELECT * FROM books LEFT JOIN users ON ...``\ ; +2. 将数据库返回结果的每一行中,属于 ``books`` 的字段加载成一个 ``Book`` 实例; +3. 然后将该行中剩下的属于 ``users`` 的字段加载成一个 ``User`` 实例; +4. 最后将 ``User`` 实例设置到 ``Book`` 实例的 ``author`` 属性上。 + +既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,\ +精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。 + + +优势与不足 +---------- + +随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM\ [30]_\ 、ORM\ [31]_\ (是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。 + +GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候\ +不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、\ +快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,\ +可以谨慎地用于生产环境了。 + +以下是近来统计到的关于 GINO 的应用案例: + +- 笔者工作上开发的一个服务:\ https://github.com/uc-cdis/metadata-service +- 还是笔者自己写的一个工具:\ https://github.com/fantix/aintq +- 一个汇率 API 服务:\ https://exchangeratesapi.io/ + + .. image:: ../images/exchangeratesapi.webp + :align: center + +- 各种 Telegram、Discord 的 Bot。 +- ArchLinux 用户包:\ https://aur.archlinux.org/packages/python-gino/ + + .. image:: ../images/archlinux.webp + :align: center + +- https://github.com/bryanforbes/gino-stubs/ +- 俄语教程:\ https://www.youtube.com/watch?v=WW4rOnfhiQY +- 高性能模板项目:\ https://github.com/leosussan/fastapi-gino-arq-uvicorn +- 还有几个商用的,但没征得同意就不贴出来了。 + +另外,GINO 还贴心地提供了中文文档\ [32]_\ ,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!): + +.. image:: ../images/docs.webp + :align: center + +GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能\ +(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。 + + +建设社会主义 +------------ + +GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 **4888 元**\ 的 PyCharm +专业版全家桶 License 一枚\ [33]_\ (没有发票)。 + +目前急需帮助的有: + +1. 各个 Web 框架插件的维护工作需要多人认领; +2. 更多的例子和文档,以及中文、俄文的翻译; +3. MySQL 的支持。 + +以及下面这些一直需要的帮助: + +1. 用 GINO,找 bug,提建议; +2. 修 bug,做功能,提 PR; +3. 维护社区,回答问题,参与讨论; +4. 最后也是最重要的:\ `去 GitHub 上给 GINO 加一颗星星! + `__ + +.. image:: ../images/happy-hacking.png + :align: center + + +关于作者 +-------- + +87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。\ +小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,\ +期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、\ +新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。\ +业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P + +特别感谢 `Tony Wang `__ 对 GINO 项目的贡献,以及\ `所有人 +`__\ 的贡献。 + + +参考文献 +-------- + +.. [1] Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado. +.. [2] Python Software Foundation. asyncio — 异步 I/O. + https://docs.python.org/zh-cn/3/library/asyncio.html. +.. [3] 维基百科. GNU. https://zh.wikipedia.org/wiki/GNU. +.. [4] 维基百科. 对象关系映射. + `https://zh.wikipedia.org/wiki/对象关系映射 + `__. +.. [5] 维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy. +.. [6] Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. + https://docs.sqlalchemy.org/en/13/core/index.html. +.. [7] Michael Bayer. Welcome to Alembic’s documentation!. + https://alembic.sqlalchemy.org/en/latest/. +.. [8] Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. + https://github.com/dahlia/awesome-sqlalchemy. +.. [9] Ricardo GarciaSilva. Does it have postgis support?. + https://github.com/python-gino/gino/issues/627. +.. [10] Fantix King. GINO 基础教程 - 增删改查. + https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations. +.. [11] Python Software Foundation. contextvars —Context Variables. + https://docs.python.org/3/library/contextvars.html. +.. [12] Fantix King. gino/aiocontextvars.py. + https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py. +.. [13] Fantix King. 数据库事务. + https://python-gino.org/docs/zh/master/how-to/transaction.html. +.. [14] Fantix King. 引擎与连接. + https://python-gino.org/docs/zh/master/explanation/engine.html. +.. [15] GINO Community. GINO Community. https://github.com/python-gino/. +.. [16] aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/. +.. [17] Sanic Community. SanicFramework. https://sanicframework.org/. +.. [18] Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/. +.. [19] Encode OSS. Starlette. https://www.starlette.io/. +.. [20] Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/. +.. [21] Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. + https://github.com/CanopyTax/asyncpgsa. +.. [22] Fantix King. 表结构定义 -GINO 引擎. + https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine. +.. [23] Fantix King. 表结构定义 - GINO core.  + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core. +.. [24] Fantix King. 表结构定义 - GINO ORM. + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm. +.. [25] MagicStack Inc. + asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. + https://github.com/MagicStack/asyncpg. +.. [26] MagicStack Inc. uvloop -Ultra fast asyncio event loop. + https://github.com/MagicStack/uvloop. +.. [27] Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/. +.. [28] Wikipedia. Plain old Java object. + https://en.wikipedia.org/wiki/Plain_old_Java_object. +.. [29] Fantix King. 加载器与关系. + https://python-gino.org/docs/zh/master/how-to/loaders.html. +.. [30] Andrey Bondar. Tortoise ORM. https://tortoise.github.io/. +.. [31] Encode OSS. ORM - An async ORM. https://github.com/encode/orm. +.. [32] Fantix King. 欢迎来到 GINO 的文档!. + https://python-gino.org/docs/zh/master/index.html. +.. [33] JetBrains s.r.o. Open Source Licenses. + https://www.jetbrains.com/community/opensource/#support. diff --git a/docs/zh/1.1b2/_sources/tutorials/fastapi.rst.txt b/docs/zh/1.1b2/_sources/tutorials/fastapi.rst.txt new file mode 100644 index 0000000..f256254 --- /dev/null +++ b/docs/zh/1.1b2/_sources/tutorials/fastapi.rst.txt @@ -0,0 +1,669 @@ +Build a FastAPI Server +====================== + +In this tutorial, we'll build a production-ready FastAPI_ server together. +The full functional example is available `here +`__. + +Our application stack will look like this: + +.. image:: ../images/gino-fastapi.svg + :align: center + + +.. _FastAPI: https://fastapi.tiangolo.com/ + + +Start a New Project +------------------- + +Instead of pip, let's use the shiny Poetry_ to manage our project. Follow the link to +`install Poetry `_, and create our new +project in an empty directory: + + +.. code-block:: console + + $ mkdir gino-fastapi-demo + $ cd gino-fastapi-demo + $ git init + $ poetry init + +Then follow the Poetry_ guide to finish the initialization - you may say "no" to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains ``gino-fastapi-demo``. + +.. _Poetry: https://python-poetry.org/ + + +Add Dependencies +---------------- + +FastAPI_ is built on top of the Starlette_ framework, so we shall use the `GINO +extension for Starlette `_. Simply run: + +.. code-block:: console + + $ poetry add gino[starlette] + +Then let's add FastAPI_, together with the lightning-fast ASGI_ server Uvicorn_, and +Gunicorn_ as a production application server: + +.. code-block:: console + + $ poetry add fastapi uvicorn gunicorn + +For database migration, we'll use Alembic_. Because it uses normal DB-API_, we need +psycopg_ here too: + +.. code-block:: console + + $ poetry add alembic psycopg2 + +At last, let's add pytest_ in the development environment for testing. We also want to +add the requests_ library to use the Starlette_ |TestClient|_: + +.. code-block:: console + + $ poetry add -D pytest requests + +.. hint:: + + With the steps above, Poetry_ will automatically create a virtualenv_ for you + behind the scene, and all the dependencies are installed there. We will assume + using this for the rest of the tutorial. But you're free to create your own + virtualenv_, and Poetry_ will honor it when it's activated. + +That's all, this is my ``pyproject.toml`` created by Poetry_, yours should look similar: + +.. code-block:: toml + + [tool.poetry] + name = "gino-fastapi-demo" + version = "0.1.0" + description = "" + authors = ["Fantix King "] + + [tool.poetry.dependencies] + python = "^3.8" + gino = {version = "^1.0", extras = ["starlette"]} + fastapi = "^0.54.1" + uvicorn = "^0.11.3" + gunicorn = "^20.0.4" + alembic = "^1.4.2" + psycopg2 = "^2.8.5" + + [tool.poetry.dev-dependencies] + pytest = "^5.4.1" + requests = "^2.23.0" + + [build-system] + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" + +.. image:: ../images/gino-fastapi-poetry.svg + :align: right + +And there's also an auto-generated ``poetry.lock`` file with the frozen versions. The +directory layout should look like the diagram on the right. Now let's add the two files +to the Git repository (we will skip showing these git operations in future steps): + +.. code-block:: console + + $ git add pyproject.toml poetry.lock + $ git commit -m 'add project dependencies' + +.. _Starlette: https://www.starlette.io/ +.. _ASGI: https://asgi.readthedocs.io/ +.. _Uvicorn: https://www.uvicorn.org/ +.. _Gunicorn: https://gunicorn.org/ +.. _Alembic: https://alembic.sqlalchemy.org/ +.. _DB-API: https://www.python.org/dev/peps/pep-0249/ +.. _psycopg: https://www.psycopg.org/ +.. _pytest: https://docs.pytest.org/ +.. _virtualenv: https://virtualenv.pypa.io/ +.. _requests: https://requests.readthedocs.io/ + + +Write a Simple Server +--------------------- + +Now let's write some Python code. + +We'll create an extra ``src`` directory to include all the Python files, as demonstrated +in the diagram below. This is known as the "`src layout +`_" providing a cleaner hierarchy. + +.. image:: ../images/gino-fastapi-src.svg + :align: right + +The root Python package of our project is named as ``gino_fastapi_demo``, under which we +will create two Python modules: + +* ``asgi`` as the ASGI entry point - we'll feed it to the ASGI server +* ``main`` to initialize our server + +Here's ``main.py``:: + + from fastapi import FastAPI + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + return app + +And we'll simply instantiate our application in ``asgi.py``:: + + from .main import get_app + + app = get_app() + +Then run ``poetry install`` to link our Python package into the ``PYTHONPATH`` in +development mode. We'll be able to start a Uvicorn development server after that: + +.. code-block:: console + + $ poetry install + Installing dependencies from lock file + + No dependencies to install or update + + - Installing gino-fastapi-demo (0.1.0) + + $ poetry run uvicorn gino_fastapi_demo.asgi:app --reload + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + INFO: Started reloader process [53010] + INFO: Started server process [53015] + INFO: Waiting for application startup. + INFO: Application startup complete. + +The ``--reload`` option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server. + +.. hint:: + + As mentioned previously, if you're in your own virtualenv, the command ``poetry run + uvicorn`` can be simplified as just ``uvicorn``. + + ``poetry run`` is a convenient shortcut to run the following command in the + virtualenv managed by Poetry. + + +Add GINO Extension +------------------ + +.. image:: ../images/gino-fastapi-config.svg + :align: right + +Now let's add GINO to our server. + +First of all, we need a way to configure the database. In this tutorial, we'll use the +`configuration system `_ from Starlette_. +Add ``src/gino_fastapi_demo/config.py`` as follows:: + + from sqlalchemy.engine.url import URL, make_url + from starlette.config import Config + from starlette.datastructures import Secret + + config = Config(".env") + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + DB_DSN = config( + "DB_DSN", + cast=make_url, + default=URL( + drivername=DB_DRIVER, + username=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + database=DB_DATABASE, + ), + ) + DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1) + DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16) + DB_ECHO = config("DB_ECHO", cast=bool, default=False) + DB_SSL = config("DB_SSL", default=None) + DB_USE_CONNECTION_FOR_REQUEST = config( + "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True + ) + DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1) + DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1) + +This config file will load from environment variable first, if not found then from a +file named ``.env`` from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this: + +.. code-block:: console + + $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload + +.. image:: ../images/gino-fastapi-env.svg + :align: right + +Or set them in the file ``.env`` (this file must not be committed into Git, remember to +add it to ``.gitignore``): + +.. code-block:: bash + + DB_HOST=localhost + DB_USER=postgres + +Now it's time to create a PostgreSQL_ database and set the connection variables +correctly here. This is usually something like ``createdb yourdbname``, but it may vary +across different platforms, so we won't cover this part in this tutorial. + +.. tip:: + + Alternatively, you could also set ``DB_DSN`` to for example + ``postgresql://user:password@localhost:5432/dbname`` to override the other individual + config values like ``DB_HOST`` defined before ``DB_DSN``. + + If defined, ``DB_DSN`` always have the higher priority over the individual ones, + regardless of where they are defined - even if ``DB_HOST`` is defined in environment + variable and ``DB_DSN`` is defined in ``.env`` file, ``DB_HOST`` is still ignored. + Default value doesn't count. + +.. image:: ../images/gino-fastapi-models.svg + :align: right + +Then, create a new Python sub-package ``gino_fastapi_demo.models`` to encapsulate +database-related code, and add the code below to +``src/gino_fastapi_demo/models/__init__.py``:: + + from gino.ext.starlette import Gino + + from .. import config + + db = Gino( + dsn=config.DB_DSN, + pool_min_size=config.DB_POOL_MIN_SIZE, + pool_max_size=config.DB_POOL_MAX_SIZE, + echo=config.DB_ECHO, + ssl=config.DB_SSL, + use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST, + retry_limit=config.DB_RETRY_LIMIT, + retry_interval=config.DB_RETRY_INTERVAL, + ) + +At last, modify ``src/gino_fastapi_demo/main.py`` to install the GINO extension: + +.. code-block:: diff + + from fastapi import FastAPI + + + +from .models import db + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + + db.init_app(app) + return app + +Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database: + +.. code-block:: console + + WARNING: Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading... + INFO: Shutting down + INFO: Waiting for application shutdown. + INFO: Application shutdown complete. + INFO: Finished server process [63562] + INFO: Started server process [63563] + INFO: Waiting for application startup. + INFO: Connecting to the database: postgresql://fantix:***@localhost + INFO: Database connection pool created: + INFO: Application startup complete. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Create Models and API +--------------------- + +.. image:: ../images/gino-fastapi-models-users.svg + :align: right + +It's time to implement the API now. Let's say we are building a user management service, +through which we could add users, list users and delete users. + +First of all, we need a database table ``users`` to store the data, mapped to a GINO +model named ``User``. We shall add the model in ``gino_fastapi_demo.models.users``:: + + from . import db + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode(), default="unnamed") + +.. image:: ../images/gino-fastapi-views.svg + :align: right + +The model definition is simple enough to explain itself. + +Then we only have to use it properly in the API implementation, for which we'll create a +new Python sub-package ``gino_fastapi_demo.views``, and a new module +``gino_fastapi_demo.views.users`` as follows:: + + from fastapi import APIRouter + from pydantic import BaseModel + + from ..models.users import User + + router = APIRouter() + + @router.get("/users/{uid}") + async def get_user(uid: int): + user = await User.get_or_404(uid) + return user.to_dict() + + class UserModel(BaseModel): + name: str + + @router.post("/users") + async def add_user(user: UserModel): + rv = await User.create(nickname=user.name) + return rv.to_dict() + + @router.delete("/users/{uid}") + async def delete_user(uid: int): + user = await User.get_or_404(uid) + await user.delete() + return dict(id=uid) + + def init_app(app): + app.include_router(router) + +The |APIRouter|_ holds our new APIs locally, and ``init_app`` is used to integrate it +into our FastAPI application. Here we want some `inversion of control`_: let's make the +APIs plugable, so that we don't have to import all possible future views manually. We +shall use the `Entry Points`_ feature to load the dependencies. Add this code below to +``gino_fastapi_demo.main``:: + + import logging + from importlib.metadata import entry_points + + logger = logging.getLogger(__name__) + + def load_modules(app=None): + for ep in entry_points()["gino_fastapi_demo.modules"]: + logger.info("Loading module: %s", ep.name) + mod = ep.load() + if app: + init_app = getattr(mod, "init_app", None) + if init_app: + init_app(app) + +.. hint:: + + If you're running Python < 3.8, you'll need this `importlib-metadata backport + `_. + +And call it in our application factory: + +.. code-block:: diff + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + db.init_app(app) + + load_modules(app) + return app + +Finally, define the entry points in ``pyproject.toml`` following the `Poetry document +for plugins `_: + +.. code-block:: toml + + [tool.poetry.plugins."gino_fastapi_demo.modules"] + "users" = "gino_fastapi_demo.views.users" + +Run ``poetry install`` again to activate the entry points - you may need to restart the +Uvicorn_ development server manually, as the reloader cannot capture the changes we made +to ``pyproject.toml``. + +Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven't created the database tables. + +.. |APIRouter| replace:: ``APIRouter`` +.. _APIRouter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control +.. _Entry Points: https://docs.python.org/3/library/importlib.metadata.html#entry-points + + +Integrate with Alembic +---------------------- + +To get started with Alembic_, run this command in the project root directory: + +.. code-block:: console + + $ poetry run alembic init migrations + +.. image:: ../images/gino-fastapi-alembic.svg + :align: right + +This will generate a new directory ``migrations`` where Alembic_ will store database +migration revisions. At the same time, an ``alembic.ini`` file is created in the project +root directory. Let's simply add all of them to Git control. + +For Alembic_ to use our data models defined with GINO (and of course the database +config), we need to modify ``migrations/env.py`` to connect with the GINO instance: + +.. code-block:: diff + + # add your model's MetaData object here + # for 'autogenerate' support + # from myapp import mymodel + # target_metadata = mymodel.Base.metadata + -target_metadata = None + +from gino_fastapi_demo.config import DB_DSN + +from gino_fastapi_demo.main import db, load_modules + + + +load_modules() + +config.set_main_option("sqlalchemy.url", str(DB_DSN)) + +target_metadata = db + +Then create our first migration revision with: + +.. code-block:: console + + $ poetry run alembic revision --autogenerate -m 'add users table' + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.autogenerate.compare] Detected added table 'users' + Generating migrations/versions/32c0feba61ea_add_users_table.py ... done + +The generated revision file should roughly look like this:: + + def upgrade(): + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("nickname", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + def downgrade(): + op.drop_table("users") + +.. hint:: + + Whenever there is a change to the database schema in the future, just modify the + GINO models and run ``alembic revision --autogenerate`` again to generate new + revisions to track the change. Remember to review the revision file - you may want + to adjust it. + +Eventually, let's apply this migration, by upgrading to the latest revision: + +.. code-block:: console + + $ poetry run alembic upgrade head + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.runtime.migration] Running upgrade -> 32c0feba61ea, add users table + +Now all the APIs should be fully operational, try with the Swagger UI. + + +Write the Tests +--------------- + +In order not to break our development database with running tests, let's create a +separate database to run tests. Apply this change to ``gino_fastapi_demo.config``: + +.. code-block:: diff + + config = Config(".env") + + +TESTING = config("TESTING", cast=bool, default=False) + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + +if TESTING: + + if DB_DATABASE: + + DB_DATABASE += "_test" + + else: + + DB_DATABASE = "gino_fastapi_demo_test" + DB_DSN = config( + +.. hint:: + + You need to run ``createdb`` to actually create the database. If you have set + ``DB_DATABASE`` in ``.env`` - e.g. ``DB_DATABASE=mydb``, the name of the testing + database should be ``mydb_test``. Or else, ``gino_fastapi_demo_test``. + +Then, let's create our pytest_ fixture in ``tests/conftest.py``:: + + import pytest + from alembic.config import main + from starlette.config import environ + from starlette.testclient import TestClient + + environ["TESTING"] = "TRUE" + + @pytest.fixture + def client(): + from gino_fastapi_demo.main import db, get_app + + main(["--raiseerr", "upgrade", "head"]) + + with TestClient(get_app()) as client: + yield client + + main(["--raiseerr", "downgrade", "base"]) + +.. image:: ../images/gino-fastapi-tests.svg + :align: right + +This fixture creates all the database tables before running the test, yield a Starlette_ +|TestClient|_, and drop all the tables with all the data after the test to maintain a +clean environment for the next test. + +Here's a sample test in ``tests/test_users.py``:: + + import uuid + + def test_crud(client): + # create + nickname = str(uuid.uuid4()) + r = client.post("/users", json=dict(name=nickname)) + r.raise_for_status() + + # retrieve + url = f"/users/{r.json()['id']}" + assert client.get(url).json()["nickname"] == nickname + + # delete + client.delete(url).raise_for_status() + assert client.get(url).status_code == 404 + +Then run the test: + +.. code-block:: console + + $ poetry run pytest + =========================== test session starts =========================== + platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 + rootdir: gino-fastapi-demo + collected 1 item + + tests/test_users.py . [100%] + + ============================ 1 passed in 1.21s ============================ + +.. |TestClient| replace:: ``TestClient`` +.. _TestClient: https://www.starlette.io/testclient/ + + +Notes for Production +-------------------- + +Given the popularity of Docker/Kubernetes, we'll build a ``Dockerfile`` for our demo: + +.. code-block:: dockerfile + + FROM python:3.8-alpine as base + + FROM base as builder + RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev + RUN pip install poetry + COPY . /src/ + WORKDIR /src + RUN python -m venv /env && . /env/bin/activate && poetry install + + FROM base + RUN apk add --no-cache postgresql-libs + COPY --from=builder /env /env + COPY --from=builder /src /src + WORKDIR /src + CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"] + +In this ``Dockerfile``, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn_ with +|UvicornWorker|_ from Uvicorn_ as the worker class for best production reliability. + +Let's review what we have in the project. + +.. image:: ../images/gino-fastapi-layout.svg + :align: center + +This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live: + +* Set ``DB_RETRY_LIMIT`` to a larger number to allow staring the application server + before the database is fully ready. +* Implement the same retry logic in ``migrations/env.py`` so that Alembic_ gets the same + functionality. +* Enable ``DB_SSL`` if needed. +* Write a ``docker-compose.yml`` for other developers to get a quick taste or even use + it for development. +* Enable CI_, install ``pytest-cov`` and use ``--cov-fail-under`` to guarantee coverage. +* Integrate static code analysis tools and security/CVE checking tools. +* Automate Alembic_ upgrade properly - e.g. after new version is deployed. +* Be aware of the common security attacks like CSRF_, XSS_, etc. +* Write load tests. + +Again, the source code of the demo is available `here +`__, +and the source of this tutorial is `here +`__. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking! + +.. |UvicornWorker| replace:: ``UvicornWorker`` +.. _UvicornWorker: https://www.uvicorn.org/deployment/#gunicorn +.. _CI: https://en.wikipedia.org/wiki/Continuous_integration +.. _CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/docs/zh/1.1b2/_sources/tutorials/tutorial.rst.txt b/docs/zh/1.1b2/_sources/tutorials/tutorial.rst.txt new file mode 100644 index 0000000..d9527f3 --- /dev/null +++ b/docs/zh/1.1b2/_sources/tutorials/tutorial.rst.txt @@ -0,0 +1,436 @@ +GINO Basics +=========== + +This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of: + +* RDBMS, especially PostgreSQL_ +* `Asynchronous programming in Python `_ + +Knowledge of SQLAlchemy_ is not required. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Introduction +------------ + +Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API. + +You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won't make your +code run faster, if not slower. Please read :doc:`/explanation/why` for more +information. + + +Installation +------------ + +To install GINO, run this command in your terminal: + +.. code-block:: console + + $ pip install gino + +This is the preferred method to install GINO, as it will always install the +most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +Alternatively, if you are using Poetry_ to manage your project dependencies, +you may want to run: + +.. code-block:: console + + $ poetry add gino + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ +.. _Poetry: https://python-poetry.org + + +Declare Models +-------------- + +First of all, we'll need a :class:`~gino.api.Gino` object, usually under the +name of ``db`` as a global variable:: + + from gino import Gino + + db = Gino() + +``db`` acts like a reference to the database, most database interactions will +go through it. + +"Model" is a basic concept in GINO, it is a Python class inherited from +:attr:`db.Model `. Each :class:`~gino.declarative.Model` +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let's declare a model:: + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + +By declaring this ``User`` class, we are actually defining a database table +named ``users``, with two columns ``id`` and ``nickname``. Note that the fixed +:attr:`~gino.declarative.Model.__tablename__` property is required. GINO +suggests singular for model names, and plural for table names. Each +:class:`db.Column ` property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to ``db`` types `here +`__ in the SQLAlchemy +documentation. + +.. note:: + + SQLAlchemy_ is a powerful ORM library for non-asynchronous programming in + Python, on top of which GINO is built. SQLAlchemy supports many popular + RDBMS including PostgreSQL and MySQL through different dialect + implementation, so that the same Python code can be compiled into different + SQL depending on the dialect you choose. GINO inherited this support too, + but for now there is only one dialect for PostgreSQL through asyncpg_. + +.. _asyncpg: https://github.com/MagicStack/asyncpg +.. _SQLAlchemy: https://www.sqlalchemy.org/ + +If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:: + + class Booking(db.Model): + __tablename__ = 'bookings' + + day = db.Column(db.Date) + booker = db.Column(db.String) + room = db.Column(db.String) + + _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey') + _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True) + _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room') + +It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +`here `__ in the +SQLAlchemy documentation. + +Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the :attr:`~gino.declarative.Model.__table_args__` attribute. In order +to e.g. define constraints in mixin classes, +:func:`~gino.declarative.declared_attr` is required. Please feel free to read +more about it in its API documentation. + + +Get Connected +------------- + +The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let's create a +PostgreSQL database for this tutorial: + +.. code-block:: console + + $ createdb gino + +Then we tell our ``db`` object to connect to this database:: + + import asyncio + + async def main(): + await db.set_bind('postgresql://localhost/gino') + + asyncio.get_event_loop().run_until_complete(main()) + +If this runs successfully, then you are connected to the newly created database. +Here ``postgresql`` indicates the database dialect to use (the default driver +is ``asyncpg``, you can explicitly specify that with ``postgresql+asyncpg://``, +or simply ``asyncpg://``), ``localhost`` is where the server is, and ``gino`` +is the name of the database. Check `here +`__ for more +information about how to compose this database URL. + +.. note:: + + Under the hood :meth:`~gino.api.Gino.set_bind` calls + :func:`~gino.create_engine` and bind the engine to this ``db`` object. GINO + engine is similar to SQLAlchemy engine, but not identical. Because GINO + engine is asynchronous, while the other is not. Please refer to the API + reference of GINO for more information. + +Now that we are connected, let's create the table in database (in the same +``main()`` method):: + + await db.gino.create_all() + +.. warning:: + + It is :meth:`db.gino.create_all `, + not :meth:`db.create_all `, because + ``db`` is inherited from SQLAlchemy :class:`~sqlalchemy.schema.MetaData`, + and :meth:`db.create_all ` is from + SQLAlchemy using non-asynchronous methods, which doesn't work with the + bound GINO engine. + + In practice :meth:`~gino.schema.GinoSchemaVisitor.create_all` is usually + not an ideal solution. To manage database schema, tool like Alembic_ is + recommended, please see how to :doc:`/how-to/alembic`. + +If you want to explicitly disconnect from the database, you can do this:: + + await db.pop_bind().close() + +Let's review the code we have so far together in one piece before moving on:: + + import asyncio + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + + async def main(): + await db.set_bind('postgresql://localhost/gino') + await db.gino.create_all() + + # further code goes here + + await db.pop_bind().close() + + + asyncio.get_event_loop().run_until_complete(main()) + +.. _Alembic: https://bitbucket.org/zzzeek/alembic + + +CRUD Operations +--------------- + +In order to operate on the database, one of GINO's core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations. + + +Create +^^^^^^ + +Let's start by creating a ``User``:: + + user = await User.create(nickname='fantix') + # This will cause GINO to execute this SQL with parameter 'fantix': + # INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname + +As mentioned previously, ``user`` object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:: + + print(f'ID: {user.id}') # 1 + print(f'Nickname: {user.nickname}') # fantix + +It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:: + + user = User(nickname='fantix') + user.nickname += ' (founder)' + await user.create() + +Retrieve +^^^^^^^^ + +To retrieve a model object from database by primary key, you can use the class +method :meth:`~gino.crud.CRUDModel.get` on the model class. Now let's retrieve +the same row:: + + user = await User.get(1) + # SQL (parameter: 1): + # SELECT users.id, users.nickname FROM users WHERE users.id = $1 + +Normal SQL queries are done through a class property +:attr:`~gino.crud.CRUDModel.query`. For example, let's retrieve all ``User`` +objects from database as a list:: + + all_users = await db.all(User.query) + # SQL: + # SELECT users.id, users.nickname FROM users + +Alternatively, you can use the ``gino`` extension on +:attr:`~gino.crud.CRUDModel.query`. This has exactly the same effect as above:: + + all_users = await User.query.gino.all() + # SQL: + # SELECT users.id, users.nickname FROM users + +.. note:: + + ``User.query`` is actually a SQLAlchemy query, with its own + non-asynchronous execution methods. GINO added this ``gino`` extension on + all executable SQLAlchemy clause objects to conveniently execute them in + the asynchronous way, so that it is even not needed to import the ``db`` + reference for execution. + +Now let's add some filters. For example, find all users with ID lower than 10:: + + founding_users = await User.query.where(User.id < 10).gino.all() + # SQL (parameter: 10): + # SELECT users.id, users.nickname FROM users WHERE users.id < $1 + +Read more `here `__ +about writing queries, because the query object is exactly from SQLAlchemy core. + +.. warning:: + + Once you get a model object, it is purely in memory and fully detached from + the database. That means, if the row is externally updated, the object + values remain unchanged. Likewise, changes made to the object won't affect + the database values. + + Also, GINO keeps no track of model objects, therefore getting the same row + twice returns two different object with identical values. Modifying one + does not magically affect the other one. + + Different than traditional ORMs, the GINO model objects are more like + objective SQL results, rather than stateful ORM objects. In order to adapt + for asynchronous programming, GINO is designed to be that simple. That's + also why GINO Is Not ORM. + +Sometimes we want to get only one object, for example getting the user by name +when logging in. There's a shortcut for this scenario:: + + user = await User.query.where(User.nickname == 'fantix').gino.first() + # SQL (parameter: 'fantix'): + # SELECT users.id, users.nickname FROM users WHERE users.nickname = $1 + +If there is no user named "fantix" in database, ``user`` will be ``None``. + +And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +:meth:`~gino.crud.CRUDModel.select` class method:: + + name = await User.select('nickname').where(User.id == 1).gino.scalar() + # SQL (parameter: 1): + # SELECT users.nickname FROM users WHERE users.id = $1 + print(name) # fantix + +Or get the count of all users:: + + population = await db.func.count(User.id).gino.scalar() + # SQL: + # SELECT count(users.id) AS count_1 FROM users + print(population) # 17 for example + + +Update +^^^^^^ + +Then let's try to make some modifications. In this example we'll mixin some +retrieve operations we just tried. :: + + # create a new user + user = await User.create(nickname='fantix') + + # get its name + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + assert name == user.nickname # they are both 'fantix' before the update + + # modification here + await user.update(nickname='daisy').apply() + # SQL (parameters: 'daisy', 1): + # UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname + print(user.nickname) # daisy + + # get its name again + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + print(name) # daisy + assert name == user.nickname # they are both 'daisy' after the update + +So :meth:`~gino.crud.CRUDModel.update` is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +:meth:`~gino.crud.UpdateRequest.apply` call makes the update happen in database. + +.. note:: + + GINO explicitly split the in-memory update and SQL update into two methods: + :meth:`~gino.crud.CRUDModel.update` and + :meth:`~gino.crud.UpdateRequest.apply`. :meth:`~gino.crud.CRUDModel.update` + will update the in-memory model object and return an + :class:`~gino.crud.UpdateRequest` object which contains all the + modifications. A following :meth:`~gino.crud.UpdateRequest.apply` on + :class:`~gino.crud.UpdateRequest` object will apply these recorded + modifications to database by executing a compiled SQL. + +.. tip:: + + :class:`~gino.crud.UpdateRequest` object has another method named + :meth:`~gino.crud.UpdateRequest.update` which works the same as the one + on model object, just that it combines the new modifications together with + the ones already recorded in current :class:`~gino.crud.UpdateRequest` + object, and it returns the same :class:`~gino.crud.UpdateRequest` object. + That means, you can chain the updates and end up with one + :meth:`~gino.crud.UpdateRequest.apply`, or make use of the + :class:`~gino.crud.UpdateRequest` object to combine several updates in a + batch. + +:meth:`~gino.crud.CRUDModel.update` on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the :meth:`~gino.crud.CRUDModel.update` on model class level, with a +bit difference:: + + await User.update.values(nickname='Founding Member ' + User.nickname).where( + User.id < 10).gino.status() + # SQL (parameter: 'Founding Member ', 10): + # UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2 + + name = await User.select('nickname').where( + User.id == 1).gino.scalar() + print(name) # Founding Member fantix + +There is no :class:`~gino.crud.UpdateRequest` here, everything is again +SQLAlchemy clause, its +`documentation `_ here for +your reference. + + +Delete +^^^^^^ + +At last. Deleting is similar to updating, but way simpler. :: + + + user = await User.create(nickname='fantix') + await user.delete() + # SQL (parameter: 1): + # DELETE FROM users WHERE users.id = $1 + print(await User.get(user.id)) # None + +.. hint:: + + Remember the model object is in memory? In the last :func:`print` + statement, even though the row is already deleted in database, the object + ``user`` still exists with its values untouched. + +Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):: + + await User.delete.where(User.id > 10).gino.status() + # SQL (parameter: 10): + # DELETE FROM users WHERE users.id > $1 + + +With basic :doc:`/how-to/crud`, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking! diff --git a/docs/zh/1.1b2/_static/css/gino.css b/docs/zh/1.1b2/_static/css/gino.css new file mode 100644 index 0000000..7f0f40b --- /dev/null +++ b/docs/zh/1.1b2/_static/css/gino.css @@ -0,0 +1,826 @@ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: 100%; + box-sizing: border-box; + font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; +} + +body { + background-color: #F0F0F0; + font-weight: 500; +} + +a { + color: #3E6CDE; +} + +code, kbd, samp { + font-family: monospace; + background-color: #f8f8f8; + border: 1px solid #f0f0f0; + padding: 0 4px; + border-radius: 2px; +} + +.highlight .hll { + display: block; +} + +strong { + font-weight: 700; +} + +:root { + --v-primary-base: #212B73; + --v-secondary-base: #AD755C; + --v-accent-base: #EEF5FF; + --v-error: #FF5252; + --v-info: #BDC8F1; + --v-info-light: #3E6CDE; + --v-success: #4CAF50; + --v-success-light: rgba(76, 175, 80, 0.2); + --v-warning: #FFC107; + --v-warning-light: rgba(255, 193, 7, 0.2); + --v-border: #444b54; + --v-border-light: #c0cee1; +} + +nav { + background-color: rgba(33, 43, 115, 0.95); + -webkit-box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); +} + +.nav-wrapper { + padding: 4px 16px; + display: flex; + align-items: center; +} + +nav .brand-logo { + position: relative; + display: flex; + align-items: center; + left: 0; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +.breadcrumbs { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + -ms-overflow-style: none; +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs .breadcrumb, +.breadcrumbs .breadcrumb::before { + content: '>'; + font-size: 16px; + white-space: nowrap; +} + + +.btn-flat { + display: flex; + align-items: center; + font-weight: 500; + margin-left: 22px; + text-decoration: none; + color: #fff; + padding: 14px; + font-size: 14px; + -webkit-transition: .2s; + transition: .2s; +} + +.btn-flat:hover { + color: #f8d230; + text-shadow: #f8d230 0 0 5px; +} + +a.brackets:before, +span.brackets:empty, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +a.footnote-reference { + vertical-align: super; + font-size: 0.8em; +} + +.footnote dt { + float: left; +} + +div.search { + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +} + +div.search i { + position: absolute; + left: 10px; + transition: 0.3s; +} + +#search:focus + i { + color: var(--v-primary-base); +} + +#search { + flex: 0.1 1 192px; + background-color: rgba(255, 255, 255, 0.16); + border-radius: 28px; + height: 38px; + padding: 0 32px 0 42px; + margin: 0; + border: none; + transition: 0.3s; + color: rgba(255, 255, 255, 0.32); + font-size: 14px; +} + +#search:focus { + border: none; + background-color: #FFFFFF; + color: #000000; +} + +#search-results { + position: absolute; + left: 0; + right: -250px; + max-height: 61.8vh; + background-color: rgba(0, 0, 0, 0.8); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + overflow: scroll; + top: 51px; + padding: 16px; +} + +#search-results li { + float: none; + line-height: 1.5; + font-size: 14px; +} + +#search-results li a { + color: #4E7CEE; + padding: 0; +} + +#search-results li .context { + padding: 0 0 24px 24px; +} +#search-results .highlighted { + color: #f8d230; +} + +.theme-dark { + color: #FFFFFF; +} + +.spacer { + flex-grow: 1; +} + +.img { + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.sidenav { + top: 64px; + z-index: 900; + box-shadow: none; + bottom: 0; + height: auto; +} + +.col.body { + padding: 0; +} + +#main-content { + padding: 0 24px; + background-color: white; +} + +.sidenav ul li:first-of-type { + display: none; +} + +.sidenav ul ul li:first-of-type { + display: block; +} + +.sidenav .caption { + font-size: 1.2em; + color: var(--v-primary-base); + padding: 16px 32px 0 48px; + position: relative; + font-weight: 600; +} + +.sidenav .caption::before { + position: absolute; + display: block; + content: " "; + width: 20px; + height: 20px; + left: 16px; + top: 20px; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.sidenav li > a { + font-size: 14px; + line-height: 14px; + padding: 12px 24px 12px 48px; + height: auto; +} + +.sidenav li li > a { + padding-left: 70px; +} + +.sidenav .caption:nth-of-type(1)::before { + background-image: url(../images/tutorials-icon.svg); +} +.sidenav .caption:nth-of-type(2)::before { + background-image: url(../images/how-to-icon.svg); +} +.sidenav .caption:nth-of-type(3)::before { + background-image: url(../images/explanation-logo.svg); +} +.sidenav .caption:nth-of-type(4)::before { + background-image: url(../images/reference-logo.svg); +} + +.sidenav a.current { + background-color: var(--v-accent-base); +} + +.github { + margin-left: 16px; + transition: 0.3s; +} + +.github:hover { + text-shadow: #FFFFFF 0 0 5px; +} + +.github i { + font-size: 40px; +} + +.right-nav.col { + padding: 0; +} + +.table-of-contents { + padding: 16px; + top: 64px; + bottom: 0; + overflow-y: auto; +} + +.table-of-contents li { + padding: 0; +} + +.table-of-contents > ul > li > a { + display: none; +} + +.table-of-contents > ul ul > li > a.active, +.table-of-contents > ul ul > li > a:hover { + padding-left: 14px; +} + +.table-of-contents > ul ul ul > li > a.active, +.table-of-contents > ul ul ul > li > a:hover { + padding-left: 30px; +} + +.table-of-contents > ul ul ul > li > a { + padding-left: 32px; +} + +.table-of-contents > ul ul ul ul > li > a.active, +.table-of-contents > ul ul ul ul > li > a:hover { + padding-left: 46px; +} + +.table-of-contents > ul ul ul ul > li > a { + padding-left: 48px; +} + +.table-of-contents a { + height: auto; + padding-top: 2px; + padding-bottom: 2px; +} + +.table-of-contents a.active, +.table-of-contents a:hover { + color: var(--v-primary-base); + border-left: 2px solid var(--v-primary-base); + font-weight: 500; +} + +.table-of-contents #version-selector hr { + margin-top: 48px; +} + +.table-of-contents #version-selector div { + margin-top: 16px; + color: #757575; +} + +.table-of-contents #version-selector a { + color: #757575; +} + +.table-of-contents .add-your-lang, +.table-of-contents .add-your-lang a, +.table-of-contents .add-your-lang a:active, +.table-of-contents .add-your-lang a:hover { + border-left: none; + font-size: 14px; + text-decoration: underline; + text-align: right; + margin-top: 8px; + color: #aaa; + font-weight: normal; +} + +.table-of-contents #version-selector span, +.table-of-contents #version-selector a, +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + border-left: none; + padding: 8px; + margin-left: 16px; +} + +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + color: var(--v-info-light); +} + +.table-of-contents #version-selector span { + color: var(--v-info-light); +} + +.version-warning { + border: 1px solid #757575; + background-color: #ffaaaa; + padding: 8px; +} + +h1 { + font-size: 32px; + margin: 32px 0 20px 0; + font-weight: 600; +} + +h2 { + font-size: 26px; + margin: 18px 0 12px 0; + font-weight: 600; +} + +h3 { + font-size: 22px; + margin: 17px 0 11px 0; + font-weight: 600; +} + +h4 { + font-size: 18px; + margin: 14px 0 9px 0; + font-weight: 600; +} + +h5 { + font-size: 16px; + margin: 13px 0 8px 0; + font-weight: 600; +} + +h6 { + font-size: 15px; + margin: 12px 0 7px 0; + font-weight: 600; +} + +a.headerlink { + padding-left: 8px; + visibility: hidden; + position: absolute; + font-family: sans-serif; +} + +:hover > a.headerlink { + visibility: visible; +} + +.section { + /*margin-top: -64px;*/ + /*padding-top: 64px;*/ +} + +.highlight { + background-color: rgba(0, 0, 0, 0.8); + clear: both; +} + +.highlight pre { + border: 1px solid var(--v-border-light); + padding: 1em; + font-size: 14px; + overflow-x: auto; +} + +.body li p { + margin: 0; +} + +.body ul li { + list-style-type: disc; + margin-left: 2rem; +} + +.body ul li li { + list-style-type: circle; +} + +.body ul li li li { + list-style-type: square; +} + +.admonition { + border-radius: 4px; + padding: 1px 1rem 40px; + box-shadow: #D1CECE 0 0 4px; + background-repeat: no-repeat; + background-position: 100% calc(100% + 20px); + background-size: 128px; +} + +.admonition-title { + font-size: 18px; + font-weight: 600; + color: #3E6CDE; +} + +.admonition-title:before { + content: " "; + display: inline-block; + width: 20px; + height: 20px; + margin: 0 8px -4px 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.admonition.note, +.admonition.seealso { + background-color: #F5FAFD; + background-image: url(../images/hmm.png); +} + +.admonition.note .admonition-title:before, +.admonition.seealso .admonition-title:before { + background-image: url(../images/icon-note.svg); +} + +.admonition.tip, +.admonition.hint { + background-color: #F5FDF6; + background-image: url(../images/aha.png); +} + +.admonition.tip { + background-image: url(../images/OK.png); +} + +.admonition.hint .admonition-title:before { + background-image: url(../images/icon-hint.svg); +} + +.admonition.warning { + background-color: #FDF5F5; + background-image: url(../images/fighting.png); +} + +.admonition.warning .admonition-title:before { + background-image: url(../images/icon-warning.svg); +} + +.admonition.tip .admonition-title:before { + background-image: url(../images/icon-info.svg); +} + +.body .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 700px; +} + +.body .section ul.boxed-nav li { + border-radius: 4px; + box-shadow: #D1CECE 0 0 4px; + padding: 10px; + margin: 25px 0 0; + width: 340px; + list-style: none; + color: #202A73; + font-weight: 500; + overflow: hidden; +} + +.body .section ul.boxed-nav li div { + background-color: #F4F7FC; + border-radius: 4px; + height: 110px; + padding: 30px 30px 0; + background-image: url(../images/box-bg-dec.svg); + background-repeat: no-repeat; + background-position: -15px -10px; +} + +.body .section ul.boxed-nav li:nth-of-type(3n) div { + background-image: url(../images/box-bg-dec-2.svg); +} + +.body .section ul.boxed-nav li:nth-of-type(3n+1) div { + background-image: url(../images/box-bg-dec-3.svg); +} + +.body .section ul.boxed-nav li a img { + width: 42px; + margin: 0 30px 30px 0; + float: left; +} + +.body .section ul.boxed-nav li p { + font-size: 18px; + color: #212B73; + line-height: 18px; + margin-bottom: 8px; +} + +.body .section ul.boxed-nav li p:nth-of-type(2) { + font-size: 14px; + word-break: break-word; +} + +.body .section ul.boxed-nav p a, +.body .section ul.boxed-nav p a:visited { + color: #212B73; +} + +p.divio { + position: absolute; + font-size: 0.8em; + color: #aaa; + margin-top: 40px; +} + +p.divio a, p.divio a:visited { + color: #aaa; +} + +.github-stars { + display: flex; + align-items: center; + padding: 0; + margin-left: 32px; + border-radius: 27px; + font-size: 14px; + font-weight: 500; +} + +.github-stars div { + border: 1px solid #fff; + line-height: 27px; + background-color: #1E2B78; + transition: 0.1s; + height: 29px; +} + +.github-stars div.left { + border-bottom-left-radius: 27px; + border-top-left-radius: 27px; + border-right: none; + padding: 0 7px 0 34px; + position: relative; +} + +.github-stars div.left .github-logo { + position: absolute; + height: 29px; + top: -1px; + left: -1px; +} + +.github-stars div.right { + border-bottom-right-radius: 27px; + border-top-right-radius: 27px; + padding: 0 7px 0 7px; + display: flex; + align-items: center; +} + +.github-stars div.right img { + height: 14px; + margin-right: 7px; +} + +.github-stars:hover { + color: white; + text-shadow: none; + box-shadow: #ffffff 0 0 5px; +} + +.btn-large.white img { + width: 32px; + height: 32px; + margin-top: 6px; +} + +.btn-large.white div { + color: #3E6CDE; + position: absolute; + bottom: 0; + width: 56px; + line-height: 18px; + font-size: 14px; + font-weight: 600; +} + +.sidenav-overlay { + z-index: 897; +} + +nav a.sidenav-trigger { + height: 24px; + margin: 16px 16px 16px 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +nav a.sidenav-trigger hr { + width: 24px; + margin: 0; + border: 0; + background-color: #fff; + height: 2px; +} + +.fixed-action-btn { + display: none; +} + +@media (min-width: 993px) { + main { + padding-left: 300px; + } + + #main-content { + padding: 0 48px; + margin: 16px; + border-radius: 4px; + } + + .admonition { + margin: 1.5rem 2rem; + padding: 1px 3rem 40px; + } + + img.align-center { + max-width: 100%; + display: block; + margin: 0 auto; + } + + img.align-left { + max-width: 100%; + float: left; + margin: 0 2rem 1.5rem 0; + } + + img.align-right { + max-width: 100%; + float: right; + margin: 0 0 1.5rem 2rem; + } + + .table-of-contents { + width: calc(25vw - 75px); + } + + p.divio { + margin-left: -47px; + } + + .btn-flat { + margin-left: 22px; + padding: 14px; + } + + .sidenav { + top: 64px; + } + + nav a.sidenav-trigger { + display: none; + } +} + +@media (max-width: 992px) { + img.align-left, + img.align-center, + img.align-right { + display: block; + margin: 1.5rem auto; + max-width: 100%; + } + + .admonition { + overflow: scroll; + } + + #main-content { + padding-bottom: 56px; + } + + #main-content p { + overflow-x: scroll; + } + + main > .row { + margin-bottom: 0; + } + + .breadcrumbs { + display: none; + } + + .btn-flat { + margin-left: 10px; + padding: 5px; + } +} + +@media (max-width: 740px) { + #search-container { + display: none; + } +} + +@media (max-width: 600px) { + .sidenav { + top: 56px; + } + + .fixed-action-btn { + display: block; + } +} + +@media (max-width: 510px) { + .github-stars { + display: none; + } +} diff --git a/docs/zh/1.1b2/_static/css/materialize.min.css b/docs/zh/1.1b2/_static/css/materialize.min.css new file mode 100644 index 0000000..74b1741 --- /dev/null +++ b/docs/zh/1.1b2/_static/css/materialize.min.css @@ -0,0 +1,13 @@ +/*! + * Materialize v1.0.0 (http://materializecss.com) + * Copyright 2014-2017 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/docs/zh/1.1b2/_static/favicon.ico b/docs/zh/1.1b2/_static/favicon.ico new file mode 100644 index 0000000..07b38bc Binary files /dev/null and b/docs/zh/1.1b2/_static/favicon.ico differ diff --git a/docs/zh/1.1b2/_static/images/OK.png b/docs/zh/1.1b2/_static/images/OK.png new file mode 100644 index 0000000..81f5433 Binary files /dev/null and b/docs/zh/1.1b2/_static/images/OK.png differ diff --git a/docs/zh/1.1b2/_static/images/aha.png b/docs/zh/1.1b2/_static/images/aha.png new file mode 100644 index 0000000..a421799 Binary files /dev/null and b/docs/zh/1.1b2/_static/images/aha.png differ diff --git a/docs/zh/1.1b2/_static/images/box-bg-dec-2.svg b/docs/zh/1.1b2/_static/images/box-bg-dec-2.svg new file mode 100644 index 0000000..0c1e246 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/box-bg-dec-2.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/zh/1.1b2/_static/images/box-bg-dec-3.svg b/docs/zh/1.1b2/_static/images/box-bg-dec-3.svg new file mode 100644 index 0000000..0d757ca --- /dev/null +++ b/docs/zh/1.1b2/_static/images/box-bg-dec-3.svg @@ -0,0 +1,25 @@ + + + + Path 10 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/docs/zh/1.1b2/_static/images/box-bg-dec.svg b/docs/zh/1.1b2/_static/images/box-bg-dec.svg new file mode 100644 index 0000000..a6e38d5 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/box-bg-dec.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/zh/1.1b2/_static/images/explanation-logo.svg b/docs/zh/1.1b2/_static/images/explanation-logo.svg new file mode 100644 index 0000000..98df7c3 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/explanation-logo.svg @@ -0,0 +1,20 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/fighting.png b/docs/zh/1.1b2/_static/images/fighting.png new file mode 100644 index 0000000..a7f6ddd Binary files /dev/null and b/docs/zh/1.1b2/_static/images/fighting.png differ diff --git a/docs/zh/1.1b2/_static/images/hmm.png b/docs/zh/1.1b2/_static/images/hmm.png new file mode 100644 index 0000000..8b60eb6 Binary files /dev/null and b/docs/zh/1.1b2/_static/images/hmm.png differ diff --git a/docs/zh/1.1b2/_static/images/how-to-icon.svg b/docs/zh/1.1b2/_static/images/how-to-icon.svg new file mode 100644 index 0000000..6506741 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/how-to-icon.svg @@ -0,0 +1,24 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/icon-hint.svg b/docs/zh/1.1b2/_static/images/icon-hint.svg new file mode 100644 index 0000000..db86c44 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/icon-hint.svg @@ -0,0 +1,27 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/icon-info.svg b/docs/zh/1.1b2/_static/images/icon-info.svg new file mode 100644 index 0000000..c4690a0 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/icon-info.svg @@ -0,0 +1,27 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/icon-note.svg b/docs/zh/1.1b2/_static/images/icon-note.svg new file mode 100644 index 0000000..03308b0 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/icon-note.svg @@ -0,0 +1,21 @@ + + + + 编组 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/icon-warning.svg b/docs/zh/1.1b2/_static/images/icon-warning.svg new file mode 100644 index 0000000..4e3ea02 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/icon-warning.svg @@ -0,0 +1,29 @@ + + + + 编组 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/language.svg b/docs/zh/1.1b2/_static/images/language.svg new file mode 100644 index 0000000..b61c7d4 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/language.svg @@ -0,0 +1,16 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/reference-logo.svg b/docs/zh/1.1b2/_static/images/reference-logo.svg new file mode 100644 index 0000000..b79d156 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/reference-logo.svg @@ -0,0 +1,22 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/images/tutorials-icon.svg b/docs/zh/1.1b2/_static/images/tutorials-icon.svg new file mode 100644 index 0000000..ff76008 --- /dev/null +++ b/docs/zh/1.1b2/_static/images/tutorials-icon.svg @@ -0,0 +1,21 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/js/documentation_options.js b/docs/zh/1.1b2/_static/js/documentation_options.js new file mode 100644 index 0000000..49725a1 --- /dev/null +++ b/docs/zh/1.1b2/_static/js/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.1.0b2', + LANGUAGE: 'zh', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/js/gino.js b/docs/zh/1.1b2/_static/js/gino.js new file mode 100644 index 0000000..1690ada --- /dev/null +++ b/docs/zh/1.1b2/_static/js/gino.js @@ -0,0 +1,642 @@ +$u = _.noConflict(); + +function splitQuery(query) { + return query.split(/\s+/); +} + +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2 +}; + +var Search = { + _index : null, + + setIndex: function(index) { + this._index = index; + }, + + performObjectSearch : function(object, otherterms) { + var filenames = this._index.filenames; + var docnames = this._index.docnames; + var objects = this._index.objects; + var objnames = this._index.objnames; + var titles = this._index.titles; + + var i; + var results = []; + + for (var prefix in objects) { + for (var name in objects[prefix]) { + var fullname = (prefix ? prefix + '.' : '') + name; + var fullnameLower = fullname.toLowerCase() + if (fullnameLower.indexOf(object) > -1) { + var score = 0; + var parts = fullnameLower.split('.'); + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower == object || parts[parts.length - 1] == object) { + score += Scorer.objNameMatch; + // matches in last name + } else if (parts[parts.length - 1].indexOf(object) > -1) { + score += Scorer.objPartialMatch; + } + var match = objects[prefix][name]; + var objname = objnames[match[1]][2]; + var title = titles[match[0]]; + // If more than one term searched for, we require other words to be + // found in the name/title/description + if (otherterms.length > 0) { + var haystack = (prefix + ' ' + name + ' ' + + objname + ' ' + title).toLowerCase(); + var allfound = true; + for (i = 0; i < otherterms.length; i++) { + if (haystack.indexOf(otherterms[i]) == -1) { + allfound = false; + break; + } + } + if (!allfound) { + continue; + } + } + var descr = objname + (', in ') + title; + + var anchor = match[3]; + if (anchor === '') + anchor = fullname; + else if (anchor == '-') + anchor = objnames[match[1]][1] + '-' + fullname; + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) { + score += Scorer.objPrio[match[2]]; + } else { + score += Scorer.objPrioDefault; + } + results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]); + } + } + } + + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch : function(searchterms, excluded, terms, titleterms) { + var docnames = this._index.docnames; + var filenames = this._index.filenames; + var titles = this._index.titles; + + var i, j, file; + var fileMap = {}; + var scoreMap = {}; + var results = []; + + // perform the search on the required terms + for (i = 0; i < searchterms.length; i++) { + var word = searchterms[i]; + var files = []; + var _o = [ + {files: terms[word], score: Scorer.term}, + {files: titleterms[word], score: Scorer.title} + ]; + // add support for partial matches + if (word.length > 2) { + for (var w in terms) { + if (w.match(word) && !terms[word]) { + _o.push({files: terms[w], score: Scorer.partialTerm}) + } + } + for (var w in titleterms) { + if (w.match(word) && !titleterms[word]) { + _o.push({files: titleterms[w], score: Scorer.partialTitle}) + } + } + } + + // no match but word was a required one + if ($u.every(_o, function(o){return o.files === undefined;})) { + break; + } + // found search word in contents + $u.each(_o, function(o) { + var _files = o.files; + if (_files === undefined) + return + + if (_files.length === undefined) + _files = [_files]; + files = files.concat(_files); + + // set score for the word in each file to Scorer.term + for (j = 0; j < _files.length; j++) { + file = _files[j]; + if (!(file in scoreMap)) + scoreMap[file] = {}; + scoreMap[file][word] = o.score; + } + }); + + // create the mapping + for (j = 0; j < files.length; j++) { + file = files[j]; + if (file in fileMap && fileMap[file].indexOf(word) === -1) + fileMap[file].push(word); + else + fileMap[file] = [word]; + } + } + + // now check if the files don't contain excluded terms + for (file in fileMap) { + var valid = true; + + // check if all requirements are matched + var filteredTermCount = // as search terms with length < 3 are discarded: ignore + searchterms.filter(function(term){return term.length > 2}).length + if ( + fileMap[file].length != searchterms.length && + fileMap[file].length != filteredTermCount + ) continue; + + // ensure that none of the excluded terms is in the search result + for (i = 0; i < excluded.length; i++) { + if (terms[excluded[i]] == file || + titleterms[excluded[i]] == file || + $u.contains(terms[excluded[i]] || [], file) || + $u.contains(titleterms[excluded[i]] || [], file)) { + valid = false; + break; + } + } + + // if we have still a valid result we can add it to the result list + if (valid) { + // select one (max) score for the file. + // for better ranking, we should calculate ranking by using words statistics like basic tf-idf... + var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]})); + results.push([docnames[file], titles[file], '', null, score, filenames[file]]); + } + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurrence, the + * latter for highlighting it. + */ + makeSearchSummary : function(htmlText, keywords, hlwords) { + var text = Search.htmlToText(htmlText); + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
    ').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; + }, + + htmlToText : function(htmlString) { + var htmlElement = document.createElement('span'); + htmlElement.innerHTML = htmlString; + $(htmlElement).find('.headerlink').remove(); + docContent = $(htmlElement).find('[role=main]')[0]; + if(docContent === undefined) { + console.warn("Content block not found. Sphinx search tries to obtain it " + + "via '[role=main]'. Could you check your theme or template."); + return ""; + } + return docContent.textContent || docContent.innerText; + }, + + query: function(query) { + this.out = $('#search-results'); + this.out.empty(); + this.output = $(''),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0'),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/js/underscore.js b/docs/zh/1.1b2/_static/js/underscore.js new file mode 100644 index 0000000..5b55f32 --- /dev/null +++ b/docs/zh/1.1b2/_static/js/underscore.js @@ -0,0 +1,31 @@ +// Underscore.js 1.3.1 +// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, +h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= +b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== +null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= +function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= +e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= +function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, +c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; +b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, +1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; +b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; +b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), +function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ +u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= +function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= +true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/docs/zh/1.1b2/_static/logo.svg b/docs/zh/1.1b2/_static/logo.svg new file mode 100644 index 0000000..78bb2b4 --- /dev/null +++ b/docs/zh/1.1b2/_static/logo.svg @@ -0,0 +1,42 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/_static/pygments.css b/docs/zh/1.1b2/_static/pygments.css new file mode 100644 index 0000000..5b510e8 --- /dev/null +++ b/docs/zh/1.1b2/_static/pygments.css @@ -0,0 +1,77 @@ +pre { line-height: 125%; margin: 0; } +td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } +span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } +td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #49483e } +.highlight { background: #272822; color: #f8f8f2 } +.highlight .c { color: #75715e } /* Comment */ +.highlight .err { color: #960050; background-color: #1e0010 } /* Error */ +.highlight .k { color: #66d9ef } /* Keyword */ +.highlight .l { color: #ae81ff } /* Literal */ +.highlight .n { color: #f8f8f2 } /* Name */ +.highlight .o { color: #f92672 } /* Operator */ +.highlight .p { color: #f8f8f2 } /* Punctuation */ +.highlight .ch { color: #75715e } /* Comment.Hashbang */ +.highlight .cm { color: #75715e } /* Comment.Multiline */ +.highlight .cp { color: #75715e } /* Comment.Preproc */ +.highlight .cpf { color: #75715e } /* Comment.PreprocFile */ +.highlight .c1 { color: #75715e } /* Comment.Single */ +.highlight .cs { color: #75715e } /* Comment.Special */ +.highlight .gd { color: #f92672 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gi { color: #a6e22e } /* Generic.Inserted */ +.highlight .go { color: #66d9ef } /* Generic.Output */ +.highlight .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #75715e } /* Generic.Subheading */ +.highlight .kc { color: #66d9ef } /* Keyword.Constant */ +.highlight .kd { color: #66d9ef } /* Keyword.Declaration */ +.highlight .kn { color: #f92672 } /* Keyword.Namespace */ +.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ +.highlight .kr { color: #66d9ef } /* Keyword.Reserved */ +.highlight .kt { color: #66d9ef } /* Keyword.Type */ +.highlight .ld { color: #e6db74 } /* Literal.Date */ +.highlight .m { color: #ae81ff } /* Literal.Number */ +.highlight .s { color: #e6db74 } /* Literal.String */ +.highlight .na { color: #a6e22e } /* Name.Attribute */ +.highlight .nb { color: #f8f8f2 } /* Name.Builtin */ +.highlight .nc { color: #a6e22e } /* Name.Class */ +.highlight .no { color: #66d9ef } /* Name.Constant */ +.highlight .nd { color: #a6e22e } /* Name.Decorator */ +.highlight .ni { color: #f8f8f2 } /* Name.Entity */ +.highlight .ne { color: #a6e22e } /* Name.Exception */ +.highlight .nf { color: #a6e22e } /* Name.Function */ +.highlight .nl { color: #f8f8f2 } /* Name.Label */ +.highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +.highlight .nx { color: #a6e22e } /* Name.Other */ +.highlight .py { color: #f8f8f2 } /* Name.Property */ +.highlight .nt { color: #f92672 } /* Name.Tag */ +.highlight .nv { color: #f8f8f2 } /* Name.Variable */ +.highlight .ow { color: #f92672 } /* Operator.Word */ +.highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ +.highlight .mf { color: #ae81ff } /* Literal.Number.Float */ +.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ +.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ +.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ +.highlight .sa { color: #e6db74 } /* Literal.String.Affix */ +.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ +.highlight .sc { color: #e6db74 } /* Literal.String.Char */ +.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ +.highlight .sd { color: #e6db74 } /* Literal.String.Doc */ +.highlight .s2 { color: #e6db74 } /* Literal.String.Double */ +.highlight .se { color: #ae81ff } /* Literal.String.Escape */ +.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ +.highlight .si { color: #e6db74 } /* Literal.String.Interpol */ +.highlight .sx { color: #e6db74 } /* Literal.String.Other */ +.highlight .sr { color: #e6db74 } /* Literal.String.Regex */ +.highlight .s1 { color: #e6db74 } /* Literal.String.Single */ +.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ +.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #a6e22e } /* Name.Function.Magic */ +.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ +.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ +.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ +.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/zh/1.1b2/explanation.html b/docs/zh/1.1b2/explanation.html new file mode 100644 index 0000000..cd8df49 --- /dev/null +++ b/docs/zh/1.1b2/explanation.html @@ -0,0 +1,240 @@ + + + + + + + + 原理说明 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/explanation/async.html b/docs/zh/1.1b2/explanation/async.html new file mode 100644 index 0000000..10991fe --- /dev/null +++ b/docs/zh/1.1b2/explanation/async.html @@ -0,0 +1,275 @@ + + + + + + + + 异步编程基础 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    异步编程基础

    +
    +

    一个经典场景

    +

    假如现在我们想要去创建自己的搜索引擎,为了创建索引数据库,家境贫寒的我们使用了一台单核电脑去爬取网页(I/O型操作),然后再将这些网页内容进行处理(计算型操作)。这个过程如下图所示:

    +../_images/why_single_task.png +

    由于我们有很多网页去要去进行爬取和处理,所以我们只能将上面的过程一个个顺序执行:

    +../_images/why_throughput.png +

    假设每个过程耗时一样,每 1 秒可以进行 2 次这样的处理,那么我可以称我们这台单核的机器的吞吐量为 2 任务/秒。如何提升当前系统的吞吐量?一个比较简单的做法就是增加 CPU 的核心数量:

    +../_images/why_multicore.png +

    如上图所示,我们超级加倍了 CPU 资源后,在未达到网络瓶颈的情况下,吞吐量也轻松翻倍,达到了 4 任务/秒。但是,我们还可以针对单个 CPU 核心增加吞吐量吗?当然可以,使用多线程:

    +../_images/why_multithreading.png +

    桥多麻袋!即便是有用了 2 个线程,我们的单核机器在 2 秒内勉勉强强才完成了 6 个任务,吞吐量也只有 2.7 任务/秒,远比 2 核心机器的 4 任务/秒的吞吐量少诶,所以所线程有啥问题呢?一起康康上面的图:

    +
      +
    • 图片上黄色色块代表额外的时间开销(啥是额外时间?马上就会提到)

    • +
    • 绿绿的色块(代表爬取网页任务)在上图所示的两个线程上可以重叠,代表可以被多个线程一起执行,但是

    • +
    • 非绿色的色块却不能彼此重叠,即不能被多线程同时一起执行。

    • +
    +

    黄色色块代表被上下文切换所消耗的时间,简单理解,就是在单核 CPU 并发处理时在多个线程中切换所消耗的时间。单核 CPU 只能在同一时间处理一件事(假设这个世界上类似超线程技术还没有被发明出来),所以为了在单核 CPU 上同时处理多线程,CPU 必须将自己的时间分成片,并且在这些被分好的时间片内处理一点点当前线程的任务,然后在下一个时间片切换到其他线程继续处理。所以刚才所说的黄色色块所消耗的额外时间实际上就是在这些线程直接来回切换所消耗的时间。虽然上图的黄色色块看上去好像占了好大一块,但实际上没这么夸张,这里是为了方便理解故意搞了个大新闻。

    +

    桥多麻袋!!还没完!既然绿色的色块可以在多线程图上重合,是不是代表这 CPU 同时处理了爬取网页的操作呢?答案是 NO!在绿色的色块覆盖的时间内,CPU 没有处理任何事情,这是因为它正在等待 HTTP 的响应(I/O 操作)。这也是使用了多线程后吞吐量还是提高到了 2.7 任务/秒,而不是降低到 1.7 任务/秒的原因。你可能会想尝试用单核 CPU 搞个多线程去处理计算密集型的任务,但这肯定没有任何提高,甚至会适合其反,正如上图所示的红色色块一样(代表处理爬取到信息的任务;实际中的上下文切换可能更加频繁一些),只能说这些任务看上去好像是同时在运行一样,但是这些任务交替执行所消耗的时间甚至比一个个的顺序执行所消耗的时间更多。这也就是为什么当前的事例中只能被称为并发,而不是并行。

    +

    你也许还会想,每多增加一个线程吞吐量可能还是会有所提升,直到因为上下文切换所消耗的时间过多而影响了系统的吞吐量,这里还没有算上每一个新的线程所占用的系统内存。通常我们不会在实际生产中在单核的 CPU 上去跑它个几百几千个线程。那么问题来了,有可能在单核 CPU 上并发处理数万个 I/O 密集型的任务吗?这就是著名的 C10k 问题,而解决这个问题的办法,正是异步 I/O:

    +../_images/why_coroutine.png +
    +

    注解

    +

    需要注意的是,异步 I/O 和协程是两个概念,但是他们经常被一起提及。在这里,为了简单起见,我们将只讲协程。

    +
    +

    看上去很奈斯嘛!现在的吞吐量是 3.7 任务/秒,已经快达到我们买不起的双核 CPU 机器的 4 任务/秒吞吐量了!虽然这些图都不是使用真是的数据,但是相比系统级别的线程而言,协程真的在上下文切换上开销更加少并且占用更少的内存,这也让协程成为解决 C10K 问题的理想选择,

    +
    +
    +

    协作式多任务处理

    +

    说了半天,协程到底是什么?

    +

    在上一个图中,你可能已经注意到了它和之前一张图片不一样的地方:在同一条线程内,绿色的色块彼此重叠了。这正是因为我们在上图中使用了异步 I/O 编程,而之前的图片中我们使用的是阻塞式的 I/O 编程。顾名思义,阻塞式的 I/O 编程会在代码执行到进行 I/O 操作时等待 I/O 结果返回,从而阻塞当前线程。因此,每条线程中同时只可能执行一个阻塞式的 I/O 操作。为了能够在阻塞式的 I/O 编程中达到并发,要么使用多线程,要么就使用多进程。对比之下,异步 I/O 允许在一条线程中同时进行数千(甚至更多)次 I/O 并发操作,此时在同一线程内,每一次的 I/O 操作只会阻塞当前协程而不是整个线程。和多线程一样,协程可以完成 I/O 的并发,但它仅仅只发生在一条线程内。

    +

    在操作系统中,线程是以抢占式多任务处理的方式进行的。举个栗子,还记得我们之前的图表吗,我们只有一个 CPU 核心,当两个线程准备处理各自爬取到的第一个网页的内容,此时第一个线程先执行,但是还没等线程一执行完毕,操作系统就打断了线程一转而去执行线程二,但是线程一的活还没干完啊,所以还要等到系统挂起线程二重新再执行线程一。如果线程内需要处理的工作很复杂的话,这里面的切换次数那就多了。但也正因为有这样的切换,每个线程才有可能去执行它当前的内容。请阅读下文,并回答文中使用了哪一种修辞手法:

    +
    Thread 1: I wanna run!
    +OS: Okay, here you go...
    +Thread 2: I wanna run!
    +OS: Urh, alright one sec ... Thread 1, hold on for a while!
    +Thread 1: Well I'm not done yet, but you are the boss.
    +OS: It won't be long. Thread 2 it's your turn now.
    +Thread 2: Yay! (&%#$@..+*&#)
    +Thread 1: Can I run now?
    +OS: Just a moment please ... Thread 2, give it a break!
    +Thread 2: Alright ... but I really need the CPU.
    +OS: You'll have it later. Thread 1, hurry up!
    +
    +
    +

    而协程,恰恰相反,它是在某个事件管家的帮助下合作完成任务的。这个事件管家同样也和协程在同一条线程内。但是和通过系统调度完成强制切换的线程不一样,事件管家只会在协程自我挂起的时候完成协程的切换。上面说了线程是抢占式地多任务,因此它是只要自己不阻塞了就想被运行,但是协程相对佛系,一切由事件管家调度,来决定哪一条协程可以被运行。当一条执行中的协程突然遇到一个需要等待的事件(比如HTTP响应),它会将控制权交换给事件管家然后默默等待事件完成,此时事件管家就去找去找另外一条已经得到事件响应的协程,然后将控制权交给它,让它执行。这种并发模式被称为`协作式多任务处理<https://en.wikipedia.org/wiki/Cooperative_multitasking>`_,如果换成拟人的修辞手法,他们大概是这样交流的:

    +
    Coroutine 1: Let me know when event A arrives. I'm done here before that.
    +Event manager: Okay. What about you, coroutine 2?
    +Coroutine 2: Um I've got nothing to do here before event B.
    +Event manager: Cool, I'll be watching.
    +Event manager: (after a while) Hey coroutine 1, event A is here!
    +Coroutine 1: Awesome! Let me see ... looks good, but I need event C now.
    +Event manager: Very well. Seems event B arrived just now, coroutine 2?
    +Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done.
    +Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while.
    +(silence)
    +Event manager: Damn, event C timed out!
    +Coroutine 1: Arrrrh gotta kill myself with an exception :S
    +Event manager: Up to you :/
    +
    +
    +

    协程遇到IO事件将会等待并挂起自己当前的任务,但它不能被其他外部因素打断。所以当有很多协程的时候,并发就是由这些协程时不时地遇到事件时挂起来实现的。当然,如果你写了一个永远不会挂起的协程,它也不会允许任何并发的产生,因为其他的协程根本没有机会去运行。另外一方面,由于不会有多个协程同时运行,我们也大可不必我们所写的担心代码会搞乱一些共享的资源。现在你能理解上图中协程和线程红色色块堆叠方式不一样的原因了吧。

    +
    +

    小技巧

    +

    在Python中,async def 用来声明协程,await 用来将协程加入一个事件循环(即上文提到的将控制权交给事件管家)。

    +
    +
    +
    +

    异步I/O的利弊

    +

    异步I/O能够在同一条线程里面完成成千上万次的并发I/O操作。这样就能够节省多线程带来的CPU上下文切换时间以及内存。因此,在面临I/O密集型的任务时,异步I/O能够有效地使用CPU和内存资源,以达到相对较高的吞吐量。

    +

    并且,在Python中可以轻松自然地编写基于协程的代码。如果业务逻辑十分复杂,基于协程完成异步I/O的代码读起来也是行云流水。

    +

    但是,对于一个任务来说,异步I/O也会降低吞吐量。比如,调用函数 recv() ,它仅仅只是阻塞地等待返回值。而如果想要它变为异步,则必须要注册读事件,等待事件循环,尝试调用 ``recv()``函数,重复以上步骤,直到结果真正返回,然后再进行回调。所以用了协程之后,整个框架的开销实际上更大了。不过幸好有了像uvloop这样优秀的事件管理模块,上述的开销已经被极大地减少。即使是这样,和阻塞式的I/O相比,总提上开销还是大了些。

    +

    想要去为异步I/O的程序计算耗时也是比较费劲的,由于协作的特性,程序的可预测性会变差。举个栗子,你可能希望当前的协程暂停1秒钟。但是此时如果另外一个协程得到了控制权,开始运行,结果计算太久,花费了2秒钟,当我们再次回到刚才的协程时,2秒钟已经实实在在地过了,这么来看,sleep(1) 这个操作已经不能理解为暂停1秒,而应该理解为至少暂停1秒。所以,你应该努力做到让那些不使用 await 进行调用、同步执行的代码尽可能快得执行,让协程真正得“合作”起来。但即使你真的做到了让同步代码快速执行,有时程序的运行候难免还是会不如你所愿,所以要时刻记住这种在执行时间上的不确定性,这样才能方便更好地排查问题。

    +

    最后,异步编程其实是复杂的。要真正写好异步的代码比想象中的难很多,而且异步的代码比同步的代码更加难以调试。尤其是当整个团队都在处理同一段异步代码时,常常会遇到意想不到的问题。因此,这里给一个比较中肯的建议:仅仅在I/O密集型的高并发场景中谨慎使用异步I/O。不是说使用了异步I/O就能够给并发性能带来的巨大提升,它更像一把双刃剑。而且如果要处理一些对时间要求十分严格的任务,请考虑再三!

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/explanation/engine.html b/docs/zh/1.1b2/explanation/engine.html new file mode 100644 index 0000000..1f5933f --- /dev/null +++ b/docs/zh/1.1b2/explanation/engine.html @@ -0,0 +1,682 @@ + + + + + + + + 引擎与连接 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    引擎与连接

    +

    GinoEngine is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together:

    +../_images/engine.png +

    Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users.

    +

    During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use.

    +
    +

    注解

    +

    In SQLAlchemy, database drivers are supposed to follow the DB-API standard, +which does not usually provide a pool implementation. Therefore, SQLAlchemy +has its own pool implementation, created directly in engine. This is where +this diagram doesn't fit SQLAlchemy.

    +
    +

    The pool creates raw connections, not the GinoConnection +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries.

    +

    On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use.

    +
    +

    注解

    +

    Another difference to SQLAlchemy here: GINO execution methods always return +final results, while in SQLAlchemy accessing the result may cause further +implicit database accesses. Therefore GINO engine immediately releases the +connection when the execution method on the engine returns, but SQLAlchemy +can only release the connection implicitly when the result data is found +exhausted.

    +

    By immediately releasing a connection, GINO may not release the related raw +connection when the raw connection was reused from another parent +connection. We'll get to this later.

    +
    +

    GINO also supports implicit execution +without having to specify an engine or connection explicitly. This is done by +binding the engine to the db instance, also known as the +MetaData or the Gino instance. +You may possibly bind a GinoConnection instance, but that +is greatly not recommended because it is very much untested.

    +

    At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +db instance on creation, therefore the models can do implicit execution too +if their db has a bind.

    +

    Then let's get to some details.

    +
    +

    Creating Engines

    +

    GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous GinoEngine is +just gino, but only available after gino is imported:

    +
    import gino, sqlalchemy
    +
    +async def main():
    +    e = await sqlalchemy.create_engine('postgresql://...', strategy='gino')
    +    # e is a GinoEngine
    +
    +
    +
    +

    小技巧

    +

    Please read this SQLAlchemy document +to learn about writing database URLs.

    +
    +

    Also the GINO strategy replaces the default driver of dialect postgresql:// +from psycopg2 to asyncpg, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +postgresql+asyncpg://... or just asyncpg://....

    +

    GINO also offers a shortcut as gino.create_engine(), which only sets the +default strategy to gino and does nothing more. So here is an identical +example:

    +
    import gino
    +
    +async def main():
    +    e = await gino.create_engine('postgresql://...')
    +    # e is also a GinoEngine
    +
    +
    +

    As you may have noticed, when using the GINO strategy, +create_engine() returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default.

    +

    For it is just SQLAlchemy create_engine(), the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!):

    +

    For Dialect:

    + +

    For Engine:

    + +

    While these parameters are discarded by GINO:

    + +

    In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from create_pool(). +For example, we can create an engine without initial connections:

    +
    e = await gino.create_engine('postgresql://...', min_size=0)
    +
    +
    +

    Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:

    +
    import sqlalchemy
    +
    +metadata = sqlalchemy.MetaData()
    +metadata.bind = 'postgresql://...'
    +
    +# or in short
    +
    +metadata = sqlalchemy.MetaData('postgresql://...')
    +
    +
    +

    This implicitly calls create_engine() under the hood. However +in GINO, creating an engine requires await, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass Gino, reverted it to simple assignment:

    +
    import gino
    +
    +db = gino.Gino()
    +
    +async def main():
    +    # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind
    +    engine = await gino.create_engine('postgresql://...')
    +    db.bind = engine
    +
    +
    +

    And provided a shortcut to do so:

    +
    engine = await db.set_bind('postgresql://...')
    +
    +
    +

    And another simpler shortcut for one-time usage:

    +
    db = await gino.Gino('postgresql://...')
    +
    +
    +

    To unset a bind and close the engine:

    +
    engine, db.bind = db.bind, None
    +await engine.close()
    +
    +
    +

    Or with a shortcut correspondingly:

    +
    await engine.pop_bind().close()
    +
    +
    +

    Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +
    +
    +

    Managing Connections

    +

    With a GinoEngine at hand, you can acquire connections +from the pool now:

    +
    conn = await engine.acquire()
    +
    +
    +

    Don't forget to release it after use:

    +
    await conn.release()
    +
    +
    +

    Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    Here conn is a GinoConnection instance. As mentioned +previously, GinoConnection is mapped to an underlying raw +connection, as shown in following diagram:

    +../_images/connection.png +

    Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: reuse and +lazy. They are keyword arguments on acquire() +and by default switched off.

    +
    +

    reuse

    +

    When acquiring a GinoConnection (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +GinoConnection (2). This is the default behavior of +acquire() with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:

    +
    async with engine.acquire() as conn1:
    +    async with engine.acquire() as conn2:
    +        # conn2 is a completely different connection than conn1
    +
    +
    +

    But sometimes conn2 may exist in a different method:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner()
    +
    +async def inner():
    +    async with engine.acquire() as conn2:
    +        # ...
    +
    +
    +

    And we probably wish inner could reuse the same raw connection in +outer to save some resource, or borrow a new one if inner is +individually called without outer:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner(conn2=None):
    +    if conn2 is None:
    +        async with engine.acquire() as conn2:
    +            # ...
    +    else:
    +        # the same ... again
    +
    +
    +

    This is exactly the scenario reuse could be useful. We can simply tell the +acquire() to reuse the most recent reusable +connection in current context by setting reuse=True, as presented in this +identical example:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner():
    +    async with engine.acquire(reuse=True) as conn2:
    +        # ...
    +
    +
    +

    Back to previous diagram, the blue GinoConnection +instances (3, 4, 6) are "reusing connections" acquired with reuse=True, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that acquire(reuse=True) always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack.

    +
    +

    小技巧

    +

    By context, we are actually referring to the context concept in +contextvars the +new module in Python 3.7, and its partial backport aiocontextvars. Simply speaking, you may +treat a series of function calls in a chain as in the same context, even if +there is an await. It's something like a thread local in asyncio.

    +
    +

    GinoConnection (2) may be created through +acquire(reuse=True) too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection.

    +

    Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +GinoConnection can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more.

    +
    +
    +

    lazy

    +

    As you may have found, GinoConnection (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set lazy=True on acquire.

    +

    A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, GinoConnection (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +
    +
    +

    On implementation level, lazy is extremely easy in +acquire(): if lazy=False then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, GinoConnection will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all GinoConnection +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +    await conn.release(permanent=False)        # release (8)
    +    await asyncio.sleep(10)                    # simulate long I/O work
    +    await conn.scalar('select now()')          # re-acquire a new raw connection,
    +                                               #   not necessarily the same (8)
    +
    +
    +

    When used together with reuse, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set lazy=False on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again:

    +../_images/connection.png +
    +
    +

    reusable

    +

    Usually, you don't have to worry about the two options reuse and lazy, +using the default acquire() will always create +a concrete GinoConnection with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use reusable=False on acquire. As shown in the diagram, the unreusable +GinoConnection is an orphan away from any stack:

    +
    async with engine.acquire():                    # (2)
    +    async with engine.acquire(reusable=False):  # the unreusable connection
    +        async with engine.acquire(reuse=True):  # (3)
    +
    +
    +

    Unreusable connections can be lazy. But it is usually meaningless to specify +both reuse=True and reusable=False at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +acquire(reuse=True, reusable=False) unless you know what it does.

    +
    +
    +

    current_connection

    +

    Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +current_connection which is always the reusable +GinoConnection at the top of current stack, or None +if current stack is empty.

    +
    +

    小技巧

    +

    The different between current_connection +and acquire(reuse=True) is, the +latter always produces a GinoConnection, while the +former may not.

    +
    +
    +
    +
    +

    Executing Queries

    +

    Once you have a GinoConnection instance, you can start +executing queries with it. There are 6 variants of the execute method: +all(), +first(), +one(), +one_or_none(), +scalar() and +status(). They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results:

    +
      +
    • all() returns all results in a +list, which may be empty when the query has no result, empty but +still a list.

    • +
    • first() returns the first result directly, +or None if there is no result at all. There is usually some optimization +behind the scene to efficiently get only the first result, instead of loading +the full result set into memory.

    • +
    • one() returns exactly one result. If there +is no result at all or if there are multiple results, an exception is raised.

    • +
    • one_or_none() is similar to +one(), but it returns None if there is +no result instead or raising an exception.

    • +
    • scalar() is similar to +first(), it returns the first value of the +first result. Quite convenient to just retrieve a scalar value from database, +like NOW(), MAX(), COUNT() or whatever generates a single value. +None is also returned when there is no result, it is up to you how to +distinguish no result and the first value is NULL.

    • +
    • status() executes the query and discard all +the query results at all. Instead it returns the execution status line as it +is, usually a textual string. Note, there may be no optimization to only +return the status without loading the results, so make your query generate +nothing if you don't want any result.

    • +
    +

    By "result", I meant RowProxy of SQLAlchemy - an +immutable row instance with both tuple and dict interfaces. +Database values are translated twice before they are eventually stored in a +RowProxy: first by the database driver (dialect) +from network payload to Python objects (see Type Conversion of +how asyncpg does this), second by SQLAlchemy +result_processor() depending on the actual +type and dialect.

    +

    The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy execute() (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +multiparams, all 4 methods will always return None discarding all +results. Likewise, the parameter values are processed twice too: first by +bind_processor() then the database driver.

    +

    GINO also supports SQLAlchemy +execution_options() provided either on +engine level, +connection level or on +queries. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling return_model and providing a model will make +all() and +first() return ORM model instance(s) instead +of RowProxy instance(s). See also +execution_options() for more information.

    +

    In addition, GINO has an iterate() method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a server-side cursor.

    +
    +
    +

    Implicit Execution

    +

    Acquire a GinoConnection and execute queries on it, that +is the most explicit way. You can also execute queries on a +GinoEngine instance. In this case, a connection will be +acquired with reuse=True for you implicitly, and released after returning:

    +
    await engine.scalar('select now()')
    +
    +
    +

    Equals to:

    +
    async with engine.acquire(reuse=True) as conn:
    +    await conn.scalar('select now()')
    +
    +
    +

    This allows you to easily write connectionless code. For example:

    +
    async def get_now():
    +    return await engine.scalar('select now()')
    +
    +async def main():
    +    async with engine.acquire():
    +        now = await get_now()
    +        await engine.status('UPDATE ...')
    +
    +
    +

    In this example, main() will take only one raw connection. get_now() +can also work alone out of any acquire() context, thanks to reuse.

    +

    Furthermore, GINO provides the same query APIs on Gino +directly. They are simply delegates to corresponding API methods on the +bind. This allows even engine-less programming:

    +
    db = gino.Gino()
    +
    +async def get_now():
    +    return await db.scalar('select now()')
    +
    +async def main():
    +    async with db.with_bind('postgresql://...'):
    +        now = await get_now()
    +        await db.status('UPDATE ...')
    +
    +
    +
    +

    注解

    +

    In this example we didn't put the two queries in an acquire() block, so +they might be executed in two different connections.

    +
    +

    At last, the SQLAlchemy implicit execution +on queries also work in GINO, under an extension named gino:

    +
    await users_table.select().gino.all()
    +
    +
    +

    By default, the extension GinoExecutor is injected on +Executable as a property of name gino +at the creation of Gino instance. Therefore, any +Executable object has the gino +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the bind of the db instance.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/explanation/sa20.html b/docs/zh/1.1b2/explanation/sa20.html new file mode 100644 index 0000000..8fbbac9 --- /dev/null +++ b/docs/zh/1.1b2/explanation/sa20.html @@ -0,0 +1,824 @@ + + + + + + + + SQLAlchemy 2.0 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    SQLAlchemy 2.0

    +

    This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes.

    +

    SQLAlchemy 2.0 will +deliver many breaking API changes, and SQLAlchemy 1.4 will be the "interim" +version for people to eventually upgrade their software to use SQLAlchemy 2.0.

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    GINO

    SQLAlchemy

    Dialect

    Comments

    1.0.x

    1.3.x

    Custom

    Current (old-)stable.

    1.1.x

    1.3.x

    Custom

    Next old-stable.

    1.2.x

    1.3.x

    Custom

    Future old-stable (maybe).

    1.4.x

    1.4.x

    Upstream

    2.0 Interim.

    2.0.x

    2.0.x

    Upstream

    Future stable.

    2.1.x

    2.0.x

    Upstream

    Future stable iterations.

    +

    To make things easier, GINO will (luckily) also follow the same versions for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only.

    +

    At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won't match SQLAlchemy versions.

    +
    +

    The Async Solution

    +

    Among all the exciting updates in SQLAlchemy 1.4 / 2.0, native async support is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet to mix asynchronous stuff into current code base, avoiding making everything +async.

    +

    Let's say we have an asynchronous method to create an asyncpg connection:

    +
    import asyncpg
    +
    +async def connect():
    +    return await asyncpg.connect("postgresql:///")
    +
    +
    +

    And an end-user method to use it:

    +
    async def main():
    +    conn = await connect()
    +    now = await conn.fetchval("SELECT now()")
    +
    +
    +

    Now instead of directly calling connect() from main(), I would like to add some +additional logic - let's say, a sanity check:

    +
    async def safe_connect():
    +    conn = await connect()
    +    try:
    +        await conn.execute("SELECT 1")
    +    except Exception:
    +        return None
    +    else:
    +        return conn
    +
    +
    +

    Then the end-user should modify main() to:

    +
     async def main():
    +     conn = await safe_connect()
    +     if conn:
    +         now = await conn.fetchval("SELECT now()")
    +
    +
    +

    OK, everything works so far, as they are all regular async code. Here's the interesting +part: safe_connect() must not be an async def method. With SQLAlchemy 1.4+, we +could:

    +
     from sqlalchemy.util import await_only, greenlet_spawn
    +
    + def sync_safe_connect():
    +     conn = await_only(connect())
    +     try:
    +         await_only(conn.execute("SELECT 1"))
    +     except Exception:
    +         return None
    +     else:
    +         return conn
    +
    + async def safe_connect():
    +     return await greenlet_spawn(sync_safe_connect)
    +
    +
    +

    Behind the scene, greenlet_spawn() runs the given "sync" method in a greenlet, which +uses await_only() to switch to the event loop and bridge the underlying async +methods. As sync_safe_connect() is just a normal Python method, you can imagine how +it works together with lots of other "sync" code asynchronously.

    +

    We're not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its "sync" code base, and still being able to provide async +APIs on top of them.

    +
    +
    +

    Async SQLAlchemy

    +

    Although greenlet might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move.

    +

    The sync library existed for years, with many assumptions like using threading.Lock +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. asyncio.Lock. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues.

    +

    As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, threading.Lock.acquire() actually works fine in a single coroutine, but 2 +concurrent coroutines +acquiring the same threading.Lock may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread.

    +

    Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base.

    +

    However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that's GINO's part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won't work +because there is no place for await in accessing attributes (GINO doesn't like such +implicitness anyways, so yeah).

    +

    To quickly get a picture of async SQLAlchemy (Core), here's a sample from SQLAlchemy:

    +
    import asyncio
    +
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def async_main():
    +    engine = create_async_engine(
    +        "postgresql+asyncpg://scott:tiger@localhost/test", echo=True,
    +    )
    +
    +    async with engine.begin() as conn:
    +        await conn.run_sync(meta.drop_all)
    +        await conn.run_sync(meta.create_all)
    +
    +        await conn.execute(
    +            t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
    +        )
    +
    +    async with engine.connect() as conn:
    +
    +        # select a Result, which will be delivered with buffered
    +        # results
    +        result = await conn.execute(select(t1).where(t1.c.name == "some name 1"))
    +
    +        print(result.fetchall())
    +
    +
    +asyncio.run(async_main())
    +
    +
    +
    +
    +

    Auto-Commit Complication

    +

    After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that BEGIN starts a transaction and +COMMIT / ROLLBACK ends it. But what is happening to SQL statements that is not +wrapped in BEGIN ... COMMIT blocks?

    +
    +

    If you do not issue a BEGIN command, then each individual statement has an +implicit BEGIN and (if successful) COMMIT wrapped around it.

    +

    —PostgreSQL Documentation, 3.4. Transactions

    +
    +

    And yes, implicit ROLLBACK if not successful. This is not directly named as an +"auto-commit" feature, but PostgreSQL does enforce it. Now imagine a database whose +"auto-commit" feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed.

    +

    PEP 249 (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only commit() and rollback() on a +connection, but no begin(). So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call commit() to persist your changes. Closing a connection will +cause pending transactions rolled back automatically.

    +
    +

    Note that if the database supports an auto-commit feature, this (the auto-commit +feature -- GINO comments) must be initially off.

    +

    —PEP 249

    +
    +

    As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +"auto-commit" has to mimic such behavior. For example, psycopg2 will automatically emit +a BEGIN to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen IDLE IN TRANSACTION?), sometimes even +holding database locks and eventually causing a deadlock storm.

    +

    To work around this workaround, PEP 249 does say:

    +
    +

    An interface method may be provided to turn it (the auto-commit feature) back on.

    +
    +

    So for psycopg2, one could do this:

    +
    import psycopg2
    +
    +conn = psycopg2.connect("postgresql:///")
    +conn.autocommit = True
    +conn.cursor().execute("SELECT now()")
    +
    +
    +

    Now the database correctly receives this SELECT statement only, without any implicit +BEGIN surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:

    +
    conn.cursor().execute("BEGIN")
    +conn.cursor().execute("UPDATE ...")
    +conn.cursor().execute("COMMIT")
    +
    +
    +

    Or 2) turn auto-commit off again:

    +
    conn.autocommit = False
    +conn.cursor().execute("UPDATE ...")
    +conn.commit()
    +
    +
    +

    I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg does provide a cleaner API, by not complying to PEP 249:

    +
    import asyncpg
    +
    +async def main():
    +    conn = await asyncpg.connect("postgresql://")
    +
    +    print(await conn.fetchval("SELECT now()"))  # SELECT now();
    +
    +    async with conn.transaction():              # BEGIN;
    +        await conn.execute("UPDATE ...")        # UPDATE ...;
    +                                                # COMMIT;
    +
    +
    +

    It's much cleaner to see what's actually happening on the wire to the database. This is +also how GINO works.

    +
    +
    +

    SQLAlchemy for DB-API

    +

    Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:

    +
    import sqlalchemy as sa
    +
    +e = sa.create_engine("postgresql:///", future=True)
    +with e.connect() as conn:
    +    conn.scalar(sa.text("SELECT now()"))
    +
    +
    +

    Only SELECT now()? No. Here's the answer:

    +
    with e.connect() as conn:                 # BEGIN; SELECT version(); ...; ROLLBACK;
    +    conn.scalar(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                          # ROLLBACK;
    +
    +
    +
    +

    注解

    +

    We are using SQLAlchemy 2.0 API for simplification, by setting future=True using +SQLAlchemy 1.4. It's way more complicated in SQLAlchemy 1.3 and I don't want to get +into that.

    +
    +

    The reason behind this is, connections have "auto-commit" turned off by default. If +there is no transaction, an implicit BEGIN will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg and +simulated a compatible DB-API. Like this:

    +
    import sqlalchemy as sa
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def main():
    +    e = create_async_engine("postgresql+asyncpg:///")
    +    async with e.connect() as conn:                  # BEGIN; SELECT version(); ...; ROLLBACK;
    +        await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                     # ROLLBACK;
    +
    +
    +

    If you want to modify the database permanently, you have to commit() the implicit +transaction explicitly:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("UPDATE ..."))    # BEGIN; UPDATE ...;
    +         await conn.commit()                          # COMMIT;
    +
    +
    +

    Or use the explicit transaction API:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Please note that, SQLAlchemy 2.0 doesn't allow soft-nested transactions. In other words, +you cannot nest 2 async with conn.begin(): blocks like this:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():      # BEGIN;
    +             async with conn.begin():  # Error: a transaction is already begun
    +                 ...
    +
    +
    +

    This limitation applies to implicit transactions too, even though it's weird:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         await conn.rollback()                        # ROLLBACK;
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Similar to Core, SQLAlchemy ORM follows the same principal. Grab a session, use +it without begin(), and when you want to commit, commit(). Or, use an explicit +transaction in a with session.begin(): block. Personally I don't like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more.

    +
    +
    +

    SQLAlchemy AUTOCOMMIT

    +

    I know you already miss the WYSIWYG asyncpg and GINO API. Hang in there, let's build +GINO 1.4 together with the SQLAlchemy AUTOCOMMIT feature.

    +

    To turn AUTOCOMMIT back on, we need to set the isolation_level to AUTOCOMMIT in +execution_options:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Hooray! No more implicit BEGIN magic. We're one step closer.

    +
    +

    注解

    +

    There is also a keyword argument:

    +
     e = create_async_engine(
    +     "postgresql+asyncpg:///",
    +     isolation_level="AUTOCOMMIT",
    + )
    +
    +
    +

    But this is implemented very differently than execution_options, and I don't +think it's working for GINO's use case.

    +
    +

    The next question is, how do we explicitly start a transaction? Let's try begin():

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    Wait a second ... we've seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we're in AUTOCOMMIT mode? Even +though the isolation_level tell the driver not to send BEGIN to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.begin():                     # no-op
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +
    +
    +

    Well, not quite what we expected. With AUTOCOMMIT set, all of begin(), commit() +and rollback() become no-ops.

    +

    Similar to the answers in psycopg2, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let's try 2):

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    It's working! According to SQLAlchemy docs, execution_options() creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well...

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                      # ROLLBACK;
    +
    +
    +

    Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same "DB-API" connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting isolation_level +modifies the value on "DB-API" connection.

    +

    Returning a SQLAlchemy connection back to the pool resets the isolation_level to its +default value, and acquiring the same connection again will initialize the +isolation_level with values from execution_options of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +isolation_level again:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         conn.execution_options(isolation_level="AUTOCOMMIT")
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Eventually we made it! 🎉

    +

    Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:

    +
    import gino
    +
    +async def main():
    +    engine = await gino.create_engine("postgresql:///")
    +    async with engine.acquire() as conn:
    +        await conn.scalar("SELECT now()")    # SELECT now();
    +
    +        async with conn.transaction():       # BEGIN;
    +            await conn.status("UPDATE ...")  # UPDATE ...;
    +                                             # COMMIT;
    +
    +
    +
    +

    提示

    +

    Now I feel that "implementing" auto-commit feature is more like restoring to the +original database behavior, and having auto-commit turned off by default should be +considered as a new feature called "auto-begin" or "implicit transaction". And it's +a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem.

    +
    +
    +
    +

    Isolation Levels

    +

    By far, we only used 2 isolation_level values:

    +
      +
    • AUTOCOMMIT

    • +
    • READ COMMITTED

    • +
    +

    AUTOCOMMIT is not a valid PostgreSQL isolation level. It's only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the "auto-begin" simulation.

    +

    READ COMMITTED is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction:

    +
    # BEGIN;
    +BEGIN
    +
    +# SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +# ROLLBACK;
    +ROLLBACK
    +
    +
    +

    To start a transaction in a different isolation level, you may:

    +
     # BEGIN ISOLATION LEVEL SERIALIZABLE;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit BEGIN in place, an implicit transaction is used. So this SQL also works +individually:

    +
    # SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +
    +

    But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels:

    +
     # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    + SET
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # BEGIN;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    + # BEGIN ISOLATION LEVEL READ COMMITTED;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  read committed
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    Then let's see how SQLAlchemy with asyncpg solves this problem:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "SERIALIZABLE"},
    +     )
    +     async with e.connect() as conn:
    +         async with conn.begin():  # BEGIN ISOLATION LEVEL SERIALIZABLE;
    +             await conn.execute(sa.text("UPDATE ..."))  # UPDATE ...;
    +                                                        # COMMIT;
    +
    +
    +

    Under the neath, SQLAlchemy is leveraging asyncpg's +Connection.transaction(isolation="...") to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions.

    +

    But there are 2 issues:

    +
      +
    • User-defined isolation level is not applied in PostgreSQL implicit transactions +(a.k.a. auto-commit statements), because no one SET SESSION.

    • +
    • asyncpg has a bug that Connection.transaction(isolation="read_committed") always +emit BEGIN without explicit isolation level, regardless of the actual default +isolation level.

    • +
    +

    The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL:

    +
     import sqlalchemy as sa
    + from sqlalchemy import event
    + from sqlalchemy.dialects.postgresql.base import PGDialect
    + from sqlalchemy.ext.asyncio import create_async_engine
    +
    +
    + async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +
    +     def set_isolation_level(dbapi_conn, record):
    +         PGDialect.set_isolation_level(
    +             e.sync_engine.dialect,
    +             dbapi_conn,
    +             "SERIALIZABLE",
    +         )
    +
    +     event.listen(e.sync_engine, "connect", set_isolation_level)
    +
    +     async with e.connect() as conn:
    +         print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL")))
    +         # Outputs: serializable
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/explanation/why.html b/docs/zh/1.1b2/explanation/why.html new file mode 100644 index 0000000..a14b52a --- /dev/null +++ b/docs/zh/1.1b2/explanation/why.html @@ -0,0 +1,410 @@ + + + + + + + + 为什么要用异步 ORM? - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    为什么要用异步 ORM?

    +

    Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly.

    +
    +

    When asyncio Helps

    +

    As Mike Bayer - the author of SQLAlchemy - pointed out, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM".

    +

    The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +异步编程基础. So the ultimate reason to use asyncio should be the application itself, +not the database.

    +

    For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead.

    +../_images/263px-Minimum-Tonne.svg.png +

    Another example is authentication using OpenID Connect Authorization Code Flow as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +Liebig's law, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave.

    +

    Arbitrary delays may happen in the database too. In PostgreSQL, you can LISTEN to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case.

    +

    In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is:

    +
    +
    +

    How to Access Database

    +

    The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here.

    +

    Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:

    +
    import asyncio
    +from fastapi import FastAPI
    +from sqlalchemy import create_engine
    +from sqlalchemy.orm import sessionmaker
    +
    +app = FastAPI()
    +e = create_engine("postgresql://localhost")
    +Session = sessionmaker(bind=e)
    +
    +
    +@app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    This follows advices found in When do I construct a Session, when do I commit it, and when do I close it? from SQLAlchemy - linking +the session scope to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is kill -9.

    +

    What just happened is called a resource starvation. While the first 10 +requests were waiting for the async work (sleep(0.1)), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default max_overflow=10) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition.

    +

    The root cause of such starvation is making asynchronous calls in a transaction +scope created implicitly by the default autocommit=False. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in transaction scopes by explicitly ending them:

    +
    @app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        session.rollback()  # return the connection to avoid starvation
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    Because of the implicit nature of typical ORMs, it's very hard to identify all such +transaction scopes and make sure there's no await in them - you may have started +an implicit transaction by simply accessing a property like current_user.name. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this.

    +

    What about thread pool?

    +

    The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool.

    +

    This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design.

    +

    In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs.

    +
    +
    +

    Asynchronous ORM Done Right

    +

    I would describe a proper asynchronous ORM as follows:

    +
    +

    Don't Starve.

    +

    The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything async.

    +

    In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:

    +
    @app.get("/")
    +async def read_root():
    +    async with db.acquire() as conn:  # }
    +        async with conn.begin():      # } These two won't block the main thread
    +            now = await db.scalar("SELECT now()")
    +            await asyncio.sleep(0.1)  # do some async work
    +            return str(now)
    +
    +
    +

    Even though this code won't cause any resource starvation, using await within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages:

    +
      +
    1. As the database connection pool is usually much smaller than the asynchronous server +concurrency, this kind of code caps the concurrency down to the DB pool level.

    2. +
    3. Taking a database connection from the pool for nothing is a waste of resource, +especially when the database pool is the shortest stave.

    4. +
    5. Database transactions should be kept short as much as possible. Because long-hanging +transactions may keep certain database locks, leading to performance issues or even +triggering a chain reaction of deadlocks.

    6. +
    +
    +
    +

    Be explicit.

    +

    With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an await or async with, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following the pattern of Twisted, asyncio is already forcing +explicit await for any asynchronous operations.

    +

    Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example:

    +
      +
    • We could design the ORM model instances to be stateless, so that the users don't have +to learn and worry about maintaining the state of the instances.

    • +
    • There shouldn't be any "buffered operations" which users could "flush" with a single +statement once for all.

    • +
    • The user should just give direct one-off commands and the ORM executes them right away.

    • +
    • Also I think the convenience tooling should be well-balanced, the user doesn't have to +guess or remember what an API means - for example, there're more than one ways to load +a many-to-one relationship, I'd prefer to write the query by myself rather than trying +to remember what "join_without_n_plus_1()" means.

    • +
    +
    +
    +

    Be productive.

    +

    Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two?

    +

    I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design.

    +

    Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again.

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/genindex.html b/docs/zh/1.1b2/genindex.html new file mode 100644 index 0000000..270c3b2 --- /dev/null +++ b/docs/zh/1.1b2/genindex.html @@ -0,0 +1 @@ +

    genindex.html

    \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to.html b/docs/zh/1.1b2/how-to.html new file mode 100644 index 0000000..3ee1e14 --- /dev/null +++ b/docs/zh/1.1b2/how-to.html @@ -0,0 +1,286 @@ + + + + + + + + 进阶用法 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    进阶用法

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/alembic.html b/docs/zh/1.1b2/how-to/alembic.html new file mode 100644 index 0000000..eb80e2c --- /dev/null +++ b/docs/zh/1.1b2/how-to/alembic.html @@ -0,0 +1,297 @@ + + + + + + + + 使用 Alembic - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    使用 Alembic

    +

    Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO.

    +

    To add migrations to project first of all, add alembic as dependency:

    +
    $ pip install --user alembic
    +
    +
    +

    When you need to set up alembic for your project.

    +

    Prepare sample project. We will have a structure:

    +
    alembic_sample/
    +    my_app/
    +        models.py
    +
    +
    +

    Inside models.py define simple DB Model with GINO:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +
    +

    Set up Alembic

    +

    This will need to be done only once. Go to the main folder of your project +alembic_sample and run:

    +
    $ alembic init alembic
    +
    +
    +

    Alembic will create a bunch of files and folders in your project directory. One of them +will be alembic.ini. Open alembic.ini (you can find it in the main project +folder alembic_sample). Now change property sqlalchemy.url = with your DB +credentials. Like this:

    +
    sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    +
    +
    +

    Next go to folder alembic/ and open env.py file. Inside the env.py file you +need to import the db object. In our case db object is db from models +modules. This is a variable that links to your Gino() instance.

    +

    Inside alembic/env.py:

    +
    from main_app.models import db
    +
    +
    +

    And change target_metadata = to:

    +
    target_metadata = db
    +
    +
    +

    That’s it. We finished setting up Alembic for a project.

    +
    +

    注解

    +

    All alembic commands must be run always from the folder that contains the +alembic.ini file.

    +
    +
    +
    +

    Create first migration revision

    +

    Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema.

    +
    $ alembic revision -m "first migration" --autogenerate --head head
    +
    +
    +

    If you have any problems relative to package imports similar to this example:

    +
    File "alembic/env.py", line 7, in <module>
    +    from main_app.models import db
    +ModuleNotFoundError: No module named 'main_app'
    +
    +
    +

    Either install your project locally with pip install -e ., poetry install or +python setup.py develop, or add you package to PYTHONPATH, like this:

    +
    $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample
    +
    +
    +

    After the successful run of alembic revision in folder alembic/versions you will +see a file with new migration.

    +
    +
    +

    Apply migration on DB

    +

    Now time to apply migration to DB. It will create tables based on you DB Models.

    +
    $ alembic upgrade head
    +
    +
    +

    Great. Now you apply your first migration. Congratulations!

    +

    Next time, when you will make any changes in DB models just do:

    +
    $ alembic revision -m "your migration description" --autogenerate --head head
    +
    +
    +

    And

    +
    alembic upgrade head
    +
    +
    +

    Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/bakery.html b/docs/zh/1.1b2/how-to/bakery.html new file mode 100644 index 0000000..f1539c6 --- /dev/null +++ b/docs/zh/1.1b2/how-to/bakery.html @@ -0,0 +1,403 @@ + + + + + + + + 预制查询 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    预制查询

    +
    +

    1.1 新版功能.

    +
    +

    Baked queries are used to boost execution performance for constantly-used queries. +Similar to the Baked Queries in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to bake them before creating the engine.

    +

    GINO provides two approaches for baked queries:

    +
      +
    1. Low-level Bakery API

    2. +
    3. High-level Gino.bake() integration

    4. +
    +
    +

    Use Bakery with Bare Engine

    +

    First, we need a bakery:

    +
    import gino
    +
    +bakery = gino.Bakery()
    +
    +
    +

    Then, let's bake some queries:

    +
    db_time = bakery.bake("SELECT now()")
    +
    +
    +

    Or queries with parameters:

    +
    user_query = bakery.bake("SELECT * FROM users WHERE id = :uid")
    +
    +
    +

    Let's assume we have this users table defined in SQLAlchemy Core:

    +
    import sqlalchemy as sa
    +
    +metadata = sa.MetaData()
    +user_table = sa.Table(
    +    "users", metadata,
    +    sa.Column("id", sa.Integer, primary_key=True),
    +    sa.Column("name", sa.String),
    +)
    +
    +
    +

    Now we can bake a similar query with SQLAlchemy Core:

    +
    user_query = bakery.bake(
    +    sa.select([user_table]).where(user.c.id == sa.bindparam("uid"))
    +)
    +
    +
    +

    These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:

    +
    engine = await gino.create_engine("postgresql://localhost/", bakery=bakery)
    +
    +
    +

    By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene.

    +

    To execute the baked queries, you could treat the BakedQuery +instances as if they are the queries themselves, for example:

    +
    now = await engine.scalar(db_time)
    +
    +
    +

    Pass in parameter values:

    +
    row = await engine.first(user_query, uid=123)
    +
    +
    +
    +
    +

    Use the Gino Integration

    +

    In a more common scenario, there will be a Gino instance, which has +usually a bind set - either explicitly or by the Web framework extensions:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        ...
    +
    +
    +

    A Bakery is automatically created in the db instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +db_time = db.bake("SELECT now()")
    +user_getter = db.bake(User.query.where(User.id == db.bindparam("uid")))
    +
    +
    +

    And the execution is also simplified with the same bind magic:

    +
    async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        print(await db_time.scalar())
    +
    +        user: User = await user_getter.first(uid=1)
    +        print(user.name)
    +
    +
    +

    To make things easier, you could even define the baked queries directly on the +model:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +    @db.bake
    +    def getter(cls):
    +        return cls.query.where(cls.id == db.bindparam("uid"))
    +
    +    @classmethod
    +    async def get(cls, uid):
    +        return await cls.getter.one_or_none(uid=uid)
    +
    +
    +

    Here GINO treats the getter() as a declared_attr() with +with_table=True, therefore it takes one positional argument cls for the User +class.

    +
    +
    +

    How to customize loaders?

    +

    If possible, you could bake the additional execution options into the query:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +)
    +
    +
    +

    The bake() method accepts keyword arguments as execution +options to e.g. simplify the example above into:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")),
    +    loader=User.load(comment="Added by loader."),
    +)
    +
    +
    +

    If the query construction is complex, bake() could also be +used as a decorator:

    +
    @db.bake
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +
    +
    +

    Or with short execution options:

    +
    @db.bake(loader=User.load(comment="Added by loader."))
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid"))
    +
    +
    +

    Meanwhile, it is also possible to override the loader at runtime:

    +
    user: User = await user_getter.load(User).first(uid=1)
    +print(user.name)  # no more comment on user!
    +
    +
    +
    +

    提示

    +

    This override won't affect the baked query - it's used only in this execution.

    +
    +
    +
    +

    What APIs are available on BakedQuery?

    +

    BakedQuery is a GinoExecutor, so it inherited +all the APIs like all(), +first(), one(), +one_or_none(), scalar(), +status(), load(), +timeout(), etc.

    +

    GinoExecutor is actually the chained .gino helper API seen +usually in queries like this:

    +
    user = await User.query.where(User.id == 123).gino.first()
    +
    +
    +

    So a BakedQuery can be seen as a normal query with the .gino +suffix, plus it is directly executable.

    +
    +

    参见

    +

    Please see API document of gino.bakery for more information.

    +
    +
    +
    +

    I don't want the prepared statements.

    +

    If you don't need all the baked queries (m) to create prepared statements for all +the active database connections (n) in the beginning, you could set +prebake=False in the engine initialization to prevent the default initial +m x n prepare calls:

    +
    e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False)
    +
    +
    +

    Or if you're using bind:

    +
    await db.set_bind("postgresql://...", prebake=False)
    +
    +
    +

    This is useful when you're depending on db.gino.create_all() to create the tables, +because the prepared statements can only be created after the table creation.

    +

    The prepared statements will then be created and cached lazily on demand.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/contributing.html b/docs/zh/1.1b2/how-to/contributing.html new file mode 100644 index 0000000..5eb5601 --- /dev/null +++ b/docs/zh/1.1b2/how-to/contributing.html @@ -0,0 +1,356 @@ + + + + + + + + 贡献 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    贡献

    +

    Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given.

    +

    您可以通过多种方式做出贡献:

    +
    +

    Types of Contributions

    +
    +

    报告错误

    +

    Report bugs at https://github.com/python-gino/gino/issues.

    +

    如果您要报告错误,请包括:

    +
      +
    • 您的操作系统名称和版本。

    • +
    • 有关本地设置的任何详细信息都可能有助于排除故障。

    • +
    • 重现错误的详细步骤。

    • +
    +
    +
    +

    修复错误

    +

    在GitHub issues 查找错误。任何人都可以将标记为“bug”和“help wanted”并且是打开状态的问题修复。

    +
    +
    +

    实现功能

    +

    在GitHub issues 查找功能。任何人都可以将标记为“增强”和“需要帮助”的打开状态的需求实现。

    +
    +
    +

    写文档

    +

    GINO需要更多的使用文档,无论是作为官方GINO文档的一部分,还是文档字符串,甚至是博客,文章等等。

    +
    +
    +

    提交反馈

    +

    The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues.

    +

    如果您要提出一项功能:

    +
      +
    • 详细解释它是如何工作的。

    • +
    • 为了更容易实现,请保持范围尽可能窄。

    • +
    • Remember that this is a volunteer-driven project, and that contributions +are welcome :)

    • +
    +
    +
    +
    +

    Get Started!

    +

    Ready to contribute? Here's how to set up gino for local development.

    +
      +
    1. Fork the gino repo on GitHub.

    2. +
    3. Clone your fork locally:

      +
      $ git clone git@github.com:your_name_here/gino.git
      +
      +
      +
    4. +
    5. Create a branch for local development:

      +
      $ cd gino/
      +$ git checkout -b name-of-your-bugfix-or-feature
      +
      +
      +
    6. +
    +

    Now you can make your changes locally.

    +
      +
    1. Create virtual environment. Example for virtualenvwrapper:

      +
      $ mkvirtualenv gino
      +
      +
      +
    2. +
    3. Activate the environment and install requirements:

      +
      $ pip install -r requirements_dev.txt
      +
      +
      +
    4. +
    5. When you're done making changes, check that your changes pass syntax checks:

      +
      $ flake8 gino tests
      +
      +
      +
    6. +
    +

    7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):

    +
    $ pytest tests
    +$ tox
    +
    +
    +
      +
    1. For docs run:

      +
      $ make docs
      +
      +
      +
    2. +
    +

    It will build and open up docs in your browser.

    +
      +
    1. Commit your changes and push your branch to GitHub:

      +
      $ git add .
      +$ git commit -m "Your detailed description of your changes."
      +$ git push origin name-of-your-bugfix-or-feature
      +
      +
      +
    2. +
    3. Submit a pull request through the GitHub website.

    4. +
    +
    +
    +

    Pull Request Guidelines

    +

    Before you submit a pull request, check that it meets these guidelines:

    +
      +
    1. The pull request should include tests.

    2. +
    3. If the pull request adds functionality, the docs should be updated. Put +your new functionality into a function with a docstring, and add the +feature to the list in README.rst.

    4. +
    5. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8. Check +https://github.com/python-gino/gino/actions?query=event%3Apull_request +and make sure that the tests pass for all supported Python versions.

    6. +
    +
    +
    +

    Tips

    +

    To run a subset of tests:

    +
    $ py.test -svx tests.test_gino
    +
    +
    +

    By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:

    +
    CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino';
    +CREATE DATABASE gino WITH OWNER = gino;
    +
    +
    +

    Then run the tests like so:

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino
    +$ py.test
    +
    +
    +

    Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):

    +
    $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
    +$ openssl rsa -in privkey.pem -passin pass:abcd -out server.key
    +$ openssl req -x509 -in server.req -text -key server.key -out server.crt
    +$ chmod 600 server.key
    +$ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    +
    +
    +

    Terminal 2 (client):

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433
    +$ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'"
    +$ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;"
    +$ pytest tests/test_aiohttp.py
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/crud.html b/docs/zh/1.1b2/how-to/crud.html new file mode 100644 index 0000000..3f45c25 --- /dev/null +++ b/docs/zh/1.1b2/how-to/crud.html @@ -0,0 +1,195 @@ + + + + + + + + 增删改查 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    增删改查

    +

    未完待续

    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/faq.html b/docs/zh/1.1b2/how-to/faq.html new file mode 100644 index 0000000..bf6bcf7 --- /dev/null +++ b/docs/zh/1.1b2/how-to/faq.html @@ -0,0 +1,558 @@ + + + + + + + + 常见问题 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    常见问题

    +
    +

    SQLAlchemy 1.4 supports asyncio, what will GINO be?

    +

    Starting from 1.4, SQLAlchemy will support asyncio. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +greenlet. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed.

    +

    Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features:

    +
      +
    • Contextual Connections (see 引擎与连接)

    • +
    • SQLAlchemy Core-based CRUD models

    • +
    • The GINO Loader system

    • +
    • Async MySQL support

    • +
    • Typing support NEW

    • +
    • Trio support NEW

    • +
    • Execution performance NEW

    • +
    +

    As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized.

    +
    +
    +

    ORM or not ORM?

    +

    GINO does perform the Object-Relational Mapping work under the +Data Mapper Pattern, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely non-ORM way.

    +
    +
    +

    Can I use features of SQLAlchemy ORM?

    +

    SQLAlchemy has two parts:

    +
      +
    • SQLAlchemy Core

    • +
    • SQLAlchemy ORM

    • +
    +

    GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO.

    +
    +
    +

    How to join?

    +

    GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know how to write join in +SQLAlchemy. +Especially, Ádám made some amazing upgrades in +GINO #113 to make join easier, so +that you can use model classes directly as if they are tables in joining:

    +
    results = await User.join(Book).select().gino.all()
    +
    +
    +
    +
    +

    How to connect to database through SSL?

    +

    It depends on the dialect and database driver. For asyncpg, keyword arguments +on asyncpg.connect() are directly +available on create_engine() or db.set_bind(). Therefore, enabling SSL is rather easy:

    +
    engine = await gino.create_engine(..., ssl=True)
    +
    +
    +
    +
    +

    What is aiocontextvars and what does it do?

    +

    It is a partial backport of the new built-in module contextvars introduced in Python +3.7. In Python 3.5 and 3.6, aiocontextvars patches loop.create_task() +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2

    +

    If you are using Python 3.7, then aiocontextvars does nothing at all.

    +
    +

    注解

    +

    This answer is for GINO 0.8 and later, please check earlier versions of +this documentation if you are using GINO 0.7.

    +
    +
    +
    +

    How to define relationships?

    +

    GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see 加载器与关系 for more information.

    +
    +
    +

    How to define index with multiple columns?

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    first_name = db.Column(db.Unicode())
    +    last_name = db.Column(db.Unicode())
    +
    +    _name_idx = db.Index('index_on_name', 'first_name', 'last_name')
    +
    +
    +

    The _name_idx is not used.

    +
    +
    +

    Is there a django admin interface for GINO?

    +

    Not quite yet, please follow this discussion.

    +
    +
    +

    How to use multiple databases for different users on the fly?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query.

    +

    In order to use multiple databases, you would need multiple +GinoEngine instances. Here's a full example using FastAPI with +lazy engine creation:

    +
    from asyncio import Future
    +from contextvars import ContextVar
    +
    +from fastapi import FastAPI, Request
    +from gino import create_engine
    +from gino.ext.starlette import Gino
    +
    +engines = {}
    +dbname = ContextVar("dbname")
    +
    +
    +class ContextualGino(Gino):
    +    @property
    +    def bind(self):
    +        e = engines.get(dbname.get(""))
    +        if e and e.done():
    +            return e.result()
    +        else:
    +            return self._bind
    +
    +    @bind.setter
    +    def bind(self, val):
    +        self._bind = val
    +
    +
    +app = FastAPI()
    +db = ContextualGino(app)
    +
    +
    +@app.middleware("http")
    +async def lazy_engines(request: Request, call_next):
    +    name = request.query_params.get("db", "postgres")
    +    fut = engines.get(name)
    +    if fut is None:
    +        fut = engines[name] = Future()
    +        try:
    +            engine = await create_engine("postgresql://localhost/" + name)
    +        except Exception as e:
    +            fut.set_exception(e)
    +            del engines[name]
    +            raise
    +        else:
    +            fut.set_result(engine)
    +    await fut
    +    dbname.set(name)
    +    return await call_next(request)
    +
    +
    +@app.get("/")
    +async def get():
    +    return dict(dbname=await db.scalar("SELECT current_database()"))
    +
    +
    +
    +
    +

    How to load complex query results?

    +

    The API doc of gino.loader explains the available loaders, and there're a few +examples in 加载器与关系 too.

    +

    Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a TupleLoader with two sub-loaders - +ModelLoader and ColumnLoader:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Be ware of the tuple in .gino.load((...)).

    +
    +
    +

    How to do bulk or batch insert / update?

    +

    For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:

    +
    new_names = ["Austin", "Ali", "Jeff", "Marissa"]
    +
    +
    +

    To quickly insert the names in one query, first construct a dict with the +{"model_key": "value"} format:

    +
    new_names_dict = [dict(name=new_name) for new_name in new_names]
    +>> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}]
    +
    +
    +

    Finally, run an insert statement on the model:

    +
    await User.insert().gino.all(new_names_dict)
    +
    +
    +
    +
    +

    How to print the executed SQL?

    +

    GINO uses the same approach from SQLAlchemy: create_engine(..., echo=True). +(Or db.set_bind(..., echo=True)) Please see also here.

    +

    If you use any extension, you can also set that in config, by db_echo or DB_ECHO.

    +
    +
    +

    How to run EXISTS SQL?

    +
    await db.scalar(db.exists().where(User.email == email).select())
    +
    +
    +
    +
    +

    How to work with Alembic?

    +

    The fact that Gino is a MetaData is the +key to use Alembic. Just import and set target_metadata = db in Alembic env.py +will do. See 使用 Alembic for more details.

    +
    +
    +

    How to join the same table twice?

    +

    This is the same pattern as described in SQLAlchemy Adjacency List Relationships, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +alias(), for example:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.ForeignKey("users.id"))
    +
    +Parent = User.alias()
    +query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
    +users = await query.gino.load(User.load(parent=Parent)).all()
    +
    +
    +
    +
    +

    How to execute raw SQL with parameters?

    +

    Wrap the SQL with text(), and use keyword arguments:

    +
    query = db.text('SELECT * FROM users WHERE id = :id_val')
    +row = await db.first(query, id_val=1)
    +
    +
    +

    You may even load the rows into model instances:

    +
    query = query.execution_options(loader=User)
    +user = await db.first(query, id_val=1)
    +
    +
    +
    +
    +

    Gino engine is not initialized?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query. If bind is not set before +this, you'll see this error:

    +
    gino.exceptions.UninitializedError: Gino engine is not initialized.
    +
    +
    +

    You could use either:

    +
      +
    • Call set_bind() or with_bind() to set the +bind on the Gino instance.

    • +
    • Use one of the Web framework extensions to set the bind for you in usually the server +start-up hook.

    • +
    • Use explicit bind for each execution, for example:

      +
      engine = await create_engine("...")
      +# ...
      +user = await User.get(request.user_id, bind=engine)
      +
      +
      +
    • +
    +
    +
    +

    How can I do SQL xxxx in GINO?

    +

    GINO uses SQLAlchemy Core queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy Table instances, and the column +attributes on GINO model classes are just SQLAlchemy Column +instances, you can use them in building your SQLAlchemy Core queries.

    +

    Alternatively, you could always execute the raw SQL directly, see How to execute raw SQL with parameters? above.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/json-props.html b/docs/zh/1.1b2/how-to/json-props.html new file mode 100644 index 0000000..d5f790a --- /dev/null +++ b/docs/zh/1.1b2/how-to/json-props.html @@ -0,0 +1,376 @@ + + + + + + + + JSON 扩展属性 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    JSON 扩展属性

    +

    GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields.

    +
    +

    Quick Start

    +
    from gino import Gino
    +from sqlalchemy.dialects.postgresql import JSONB
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +    birthday = db.DateTimeProperty()
    +
    +
    +

    The age and birthday are JSON properties stored in the profile column. You +may use them the same way as a normal GINO model field:

    +
    u = await User.create(name="daisy", age=18)
    +print(u.name, u.age)  # daisy 18
    +
    +
    +
    +

    注解

    +

    profile is the default column name for all JSON properties in a model. If you +need a different column name for some JSON properties, you'll need to specify +explicitly:

    +
    audit_profile = db.Column(JSON, nullable=False, server_default="{}")
    +
    +access_log = db.ArrayProperty(prop_name="audit_profile")
    +abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
    +
    +
    +
    +

    Using JSON properties in queries is supported:

    +
    await User.query.where(User.age > 16).gino.all()
    +
    +
    +

    This is simply translated into a native JSON query like this:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
    +
    +
    +

    Datetime type is very much the same:

    +
    from datetime import datetime
    +
    +await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
    +
    +
    +

    And the generated SQL:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
    +-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
    +
    +
    +

    Here's a list of all the supported JSON properties:

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    JSON 扩展属性

    Python type

    JSON type

    Database Type

    StringProperty

    str

    string

    text

    IntegerProperty

    int

    number

    int

    BooleanProperty

    bool

    boolean

    boolean

    DateTimeProperty

    datetime

    string

    text

    ObjectProperty

    dict

    object

    JSON

    ArrayProperty

    list

    array

    JSON

    +
    +
    +

    Hooks

    +

    JSON property provides 2 instance-level hooks to customize the data:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @age.before_set
    +    def age(self, val):
    +        return val - 1
    +
    +    @age.after_get
    +    def age(self, val):
    +        return val + 1
    +
    +u = await User.create(name="daisy", age=18)
    +print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
    +
    +
    +

    And 1 class-level hook to customize the SQLAlchemy expression of the property:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    height = db.JSONProperty()
    +
    +    @height.expression
    +    def height(cls, exp):
    +        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
    +
    +
    +
    +
    +

    Create Index on JSON Properties

    +

    We'll need to use declared_attr() to wait until the model class +is initialized. The rest is very much the same as defining a usual index:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @db.declared_attr
    +    def age_idx(cls):
    +        return db.Index("age_idx", cls.age)
    +
    +
    +

    This will lead to the SQL below executed if you run db.gino.create_all():

    +
    CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
    +
    +
    +
    +

    警告

    +

    Alembic doesn't support auto-generating revisions for functional indexes yet. You'll +need to manually edit the revision. Please follow this issue for updates.

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/loaders.html b/docs/zh/1.1b2/how-to/loaders.html new file mode 100644 index 0000000..f7b337a --- /dev/null +++ b/docs/zh/1.1b2/how-to/loaders.html @@ -0,0 +1,639 @@ + + + + + + + + 加载器与关系 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    加载器与关系

    +

    Loaders are used to load database row results into objects.

    +

    GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined.

    +
    +

    Model Loader

    +

    The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:

    +
    query = db.select([User])
    +rows = await query.gino.all()
    +
    +
    +

    In order to load rows into User objects, you can provide an execution +option loader with a new ModelLoader instance:

    +
    from gino.loader import ModelLoader
    +
    +query = db.select([User])
    +query = query.execution_options(loader=ModelLoader(User))
    +users = await query.gino.all()
    +
    +
    +

    The ModelLoader would then load each database row into a +User object. As this is frequently used, GINO made it a shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User.load())
    +users = await query.gino.all()
    +
    +
    +

    And another shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User)
    +users = await query.gino.all()
    +
    +
    +
    +

    小技巧

    +

    User as loader is transformed into ModelLoader(User) by +Loader.get(), explained later in "Loader +Expression".

    +
    +

    And again:

    +
    query = db.select([User])
    +users = await query.gino.load(User).all()
    +
    +
    +

    This is identical to the normal CRUD query:

    +
    users = await User.query.gino.all()
    +
    +
    +
    +
    +

    Loader Expression

    +

    So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than ModelLoader, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders.

    +

    Here is an example using all loaders at once:

    +
    uid, user, sep, cols = await db.select([User]).gino.load(
    +    (
    +        User.id,
    +        User,
    +        '|',
    +        lambda row, ctx: len(row),
    +    )
    +).first()
    +
    +
    +

    Let's check this piece by piece. Overall, the argument of +load() is a tuple. This is interpreted into a +TupleLoader, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a TupleLoader is a tuple.

    +

    Column in Loader Expressions are interpreted as +ColumnLoader. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, ColumnLoader uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use ColumnLoader, +you'll have to declare columns for the query:

    +
    now = db.Column('time', db.DateTime())
    +result = await db.first(db.text(
    +    'SELECT now() AT TIME ZONE \'UTC\''
    +).columns(
    +    now,
    +).gino.load(
    +    ('now:', now)
    +).query)
    +print(*result)  # now: 2018-04-08 08:23:02.431847
    +
    +
    +

    Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +ModelLoader. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values.

    +
    +

    小技巧

    +

    For a complex loader expression, the same row is given to all loaders, so +it doesn't matter User.id is already used before the model loader.

    +
    +

    The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +map(), the return value of the call will be the loaded result.

    +

    At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the '|' separator, it will show up as the third item +in every result returned by the query.

    +
    +
    +

    Many-to-One Relationship

    +

    A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +

    So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:

    +
    async for child in Child.load(parent=Parent).gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    As you may have noticed, Child.load is exactly the shortcut to create +ModelLoader in the very first example. With some +additional keyword arguments, Child.load(parent=Parent) is still a +ModelLoader for Child, the model loader is at the +same time a query builder. It is identical to do this:

    +
    async for child in Child.load(parent=Parent).query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    The query dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The Loader simply forwarded unknown +attributes to its query, that's why .query can +be omitted.

    +

    For ModelLoader, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using setattr(). For example, Parent is +interpreted as ModelLoader(Parent) which loads Parent instances, and +Parent instances are set as the parent attribute of the outer Child +instance.

    +
    +

    警告

    +

    If multiple children references the same parent, then each child owns a +unique parent instance with identical values.

    +
    +
    +

    小技巧

    +

    You don't have to define parent attribute on Child. But if you do, +you gain the ability to customize how parent is stored or retrieved. For +example, let's store the parent instance as _parent:

    +
    class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    _parent = None
    +
    +    @property
    +    def parent(self):
    +        return self._parent
    +
    +    @parent.setter
    +    def parent(self, value):
    +        self._parent = value
    +
    +
    +
    +

    The query builder works recursively. For ModelLoader, it +uses LEFT OUTER JOIN to connect the FROM clauses, in order to achieve +many-to-one scenario. The ON clause is determined automatically by foreign +keys. You can also customize the ON clause in case there is no foreign key +(a promise is a promise):

    +
    loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +async for child in loader.query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    And subloaders can be nested:

    +
    subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
    +
    +
    +

    By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values.

    +
    +
    +

    Self Referencing

    +
    +

    警告

    +

    Experimental feature.

    +
    +

    Self referencing is usually used to create a tree-like structure. For example:

    +
    class Category(db.Model):
    +    __tablename__ = 'categories'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    +
    +
    +

    In order to load leaf categories with their parents, an alias is needed:

    +
    Parent = Category.alias()
    +
    +
    +

    Then the query would be something like this:

    +
    parents = db.select([Category.parent_id])
    +query = Category.load(parent=Parent.on(
    +    Category.parent_id == Parent.id
    +)).where(
    +    ~Category.id.in_(db.select([Category.alias().parent_id]))
    +)
    +async for c in query.gino.iterate():
    +    print(f'Leaf: {c.id}, Parent: {c.parent.id}')
    +
    +
    +

    The generated SQL looks like this:

    +
    SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id
    +  FROM categories LEFT OUTER JOIN categories AS categories_1
    +    ON categories.parent_id = categories_1.id
    + WHERE categories.id NOT IN (
    +           SELECT categories_2.parent_id
    +             FROM categories AS categories_2
    +       )
    +
    +
    +
    +
    +

    Other Relationships

    +

    GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    @children.setter
    +    def add_child(self, child):
    +        self._children.add(child)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child)).all()
    +
    +
    +

    Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the add_child setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +parent.add_child = new_child.

    +

    Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries.

    +

    GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._child = None
    +
    +    @property
    +    def child(self):
    +        return self._child
    +
    +    @child.setter
    +    def child(self, child):
    +        self._child = child
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
    +
    +
    +

    Similarly, you can build many-to-many relationships in the same way:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    def add_child(self, child):
    +        self._children.add(child)
    +        child._parents.add(self)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._parents = set()
    +
    +    @property
    +    def parents(self):
    +        return self._parents
    +
    +
    +class ParentXChild(db.Model):
    +    __tablename__ = 'parents_x_children'
    +
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
    +
    +
    +query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
    +
    +
    +

    Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ParentXChild instances.

    +
    +
    +

    Advanced Usage of Loaders

    +

    You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For example, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Using alias to get ID-ascending pairs from the same table:

    +
    ua1 = User.alias()
    +ua2 = User.alias()
    +join_query = select([ua1, ua2]).where(ua1.id < ua2.id)
    +loader = ua1.load('id'), ua2.load('id')
    +result = await join_query.gino.load(loader).all()
    +print(result)  # e.g. [(1, 2), (1, 3), (2, 3)]
    +
    +
    +

    Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/pool.html b/docs/zh/1.1b2/how-to/pool.html new file mode 100644 index 0000000..b747823 --- /dev/null +++ b/docs/zh/1.1b2/how-to/pool.html @@ -0,0 +1,217 @@ + + + + + + + + 连接池 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    连接池

    +

    Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +NullPool), and users can define their own pools. +The base class should be Pool.

    +

    To use non-default pools in raw GINO:

    +
    from gino.dialects.asyncpg import NullPool
    +create_engine('postgresql://...', pool_class=NullPool)
    +
    +
    +

    To use non-default pools in extensions (taking Sanic as an example):

    +
    from gino.dialects.asyncpg import NullPool
    +from gino.ext.sanic import Gino
    +
    +app = sanic.Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_KWARGS = dict(
    +    pool_class=NullPool,
    +)
    +db = Gino()
    +db.init_app(app)
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/schema.html b/docs/zh/1.1b2/how-to/schema.html new file mode 100644 index 0000000..7cd0bc4 --- /dev/null +++ b/docs/zh/1.1b2/how-to/schema.html @@ -0,0 +1,419 @@ + + + + + + + + 表结构定义 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    表结构定义

    +

    There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy Table.

    +
    +

    GINO Engine

    +

    This is the minimized way to use GINO - using only +GinoEngine (and GinoConnection +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two.

    +

    For example, the table declaration is the same as SQLAlchemy core tutorial:

    +
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    +
    +metadata = MetaData()
    +
    +users = Table(
    +    'users', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('name', String),
    +    Column('fullname', String),
    +)
    +
    +addresses = Table(
    +    'addresses', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('user_id', None, ForeignKey('users.id')),
    +    Column('email_address', String, nullable=False)
    +)
    +
    +
    +
    +

    注解

    +

    When using GINO Engine only, it is usually your own business to create the +tables with either create_all() on a +normal non-async SQLAlchemy engine, or using Alembic. However it is still +possible to be done with GINO if it had to:

    +
    import gino
    +from gino.schema import GinoSchemaVisitor
    +
    +async def main():
    +    engine = await gino.create_engine('postgresql://...')
    +    await GinoSchemaVisitor(metadata).create_all(engine)
    +
    +
    +
    +

    Then, construct queries, in SQLAlchemy core too:

    +
    ins = users.insert().values(name='jack', fullname='Jack Jones')
    +
    +
    +

    So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:

    +
    async def main():
    +    engine = await gino.create_engine('postgresql://localhost/gino')
    +    conn = await engine.acquire()
    +    await conn.status(ins)
    +    print(await conn.all(users.select()))
    +    # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Here create_engine() creates a GinoEngine, +then acquire() checks out a +GinoConnection, and +status() executes the insert and returns the +status text. This works similarly as SQLAlchemy +execute() - they take the same parameters +but return a bit differently. There are also other similar query APIs:

    +
      +
    • all() returns a list of +RowProxy

    • +
    • first() returns one +RowProxy, or None

    • +
    • one() returns one +RowProxy

    • +
    • one_or_none() returns one +RowProxy, or None

    • +
    • scalar() returns a single value, or +None

    • +
    • iterate() returns an asynchronous iterator +which yields RowProxy

    • +
    +

    Please go to their API for more information.

    +
    +
    +

    GINO Core

    +

    In previous scenario, GinoEngine must not be set to +metadata.bind because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of MetaData as Gino, +usually instantiated globally under the name of db. It can be used as a +normal MetaData still offering some conveniences:

    +
      +
    • It delegates most public types you can access on sqlalchemy

    • +
    • It works with both normal SQLAlchemy engine and asynchronous GINO engine

    • +
    • It exposes all query APIs on GinoConnection level

    • +
    • It injects two gino extensions on SQLAlchemy query clauses and schema +items, allowing short inline execution like users.select().gino.all()

    • +
    • It is also the entry for the third scenario, see later

    • +
    +

    Then we can achieve previous scenario with less code like this:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +users = db.Table(
    +    'users', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('name', db.String),
    +    db.Column('fullname', db.String),
    +)
    +
    +addresses = db.Table(
    +    'addresses', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('user_id', None, db.ForeignKey('users.id')),
    +    db.Column('email_address', db.String, nullable=False)
    +)
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await users.insert().values(
    +            name='jack',
    +            fullname='Jack Jones',
    +        ).gino.status()
    +        print(await users.select().gino.all())
    +        # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but sqlalchemy seems +never imported. This is useful when ORM is unwanted.

    +
    +

    小技巧

    +

    asyncpgsa does the same thing, +but in a conceptually reversed way - instead of having asyncpg work for +SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that +way too because GINO is inspired by asyncpgsa). Either way works fine, it's +just a matter of taste of whose API style to use, SQLAlchemy or asyncpg.

    +
    +
    +
    +

    GINO ORM

    +

    If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    fullname = db.Column(db.String)
    +
    +
    +class Address(db.Model):
    +    __tablename__ = 'addresses'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    user_id = db.Column(None, db.ForeignKey('users.id'))
    +    email_address = db.Column(db.String, nullable=False)
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await User.create(name='jack', fullname='Jack Jones')
    +        print(await User.query.gino.all())
    +        # Outputs: [<User object at 0x10a8ba860>]
    +
    +
    +
    +

    重要

    +

    The __tablename__ is a mandatory field to define a concrete model.

    +
    +

    As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in db. The class style is just +more declarative. Instead of users.c.name, you can now access the column by +User.name. The implicitly created Table is +available at User.__table__ and Address.__table__. You can use anything +that works in GINO core here.

    +
    +

    注解

    +

    Column names can be different as a class property and database column. +For example, name can be declared as +nickname = db.Column('name', db.Unicode(), default='noname'). In this +example, User.nickname is used to access the column, while in database, +the column name is name.

    +

    What's worth mentioning is where raw SQL statements are used, or +TableClause is involved, like User.insert(), the original name is +required to be used, because in this case, GINO has no knowledge about the +mappings.

    +
    +
    +

    小技巧

    +

    db.Model is a dynamically created parent class for your models. It is +associated with the db on initialization, therefore the table is put in +the very db when you declare your model class.

    +
    +

    Things become different when it comes to CRUD. You can use model level methods +to directly create() a model instance, instead of +inserting a new row. Or delete() a model instance +without needing to specify the where clause manually. Query returns model +instances instead of RowProxy, and row values are +directly available as attributes on model instances. See also: +增删改查.

    +

    After all, GinoEngine is always in use. Next let's dig +more into it.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/how-to/transaction.html b/docs/zh/1.1b2/how-to/transaction.html new file mode 100644 index 0000000..fa8b216 --- /dev/null +++ b/docs/zh/1.1b2/how-to/transaction.html @@ -0,0 +1,303 @@ + + + + + + + + 数据库事务 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    数据库事务

    +

    It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an await will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it.

    +
    +

    Basic usage

    +

    Transactions belong to GinoConnection. The most common +way to use transactions is through an async with statement:

    +
    async with connection.transaction() as tx:
    +    await connection.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    This guarantees a transaction is opened when entering the async with block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at raw_transaction, but in +most cases you don't need to touch it.

    +

    GINO provides two convenient shortcuts to end the transaction early:

    + +

    They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the async with block after +raise_commit() or +raise_rollback() is skipped. The +internal exception is inherited from BaseException so that normal try +... except Exception block can't trap it. This exception stops propagating at +the end of async with block, so you don't need to worry about handling it.

    +

    Transactions can also be started on a GinoEngine:

    +
    async with engine.transaction() as tx:
    +    await engine.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    Here a GinoConnection is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The GinoConnection instance is accessible at +tx.connection. Other than +that, everything else is the same.

    +
    +

    重要

    +

    The implicit connection is by default borrowed with reuse=True. That +means using transaction() of +GinoEngine within a connection context is the same as +calling transaction() of the current +connection without having to reference it, no separate connection shall be +created.

    +
    +

    Similarly, if your Gino instance has a bind, you may also do +the same on it:

    +
    async with db.transaction() as tx:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +
    +
    +

    Nested Transactions

    +

    Transactions can be nested, nested transaction will create a savepoint as for +now on asyncpg. A similar example from asyncpg doc:

    +
    async with connection.transaction() as tx1:
    +    await connection.status('CREATE TABLE mytab (a int)')
    +
    +    # Create a nested transaction:
    +    async with connection.transaction() as tx2:
    +        await connection.status('INSERT INTO mytab (a) VALUES (1), (2)')
    +        # Rollback the nested transaction:
    +        tx2.raise_rollback()
    +
    +    # Because the nested transaction was rolled back, there
    +    # will be nothing in `mytab`.
    +    assert await connection.all('SELECT a FROM mytab') == []
    +
    +
    +

    As you can see, the raise_rollback() +breaks only the async with block of the specified tx2, the outer +transaction tx1 just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate.

    +

    Because of the default reusing behavior, transactions on engine or db +follows the same nesting rules. Please see +GinoTransaction for more information.

    +
    +
    +

    Manual Control

    +

    Other than using async with, you can also manually control the +transaction:

    +
    tx = await connection.transaction()
    +try:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    You can't use raise_commit() or +raise_rollback() here, similarly it is +prohibited to use commit() and +rollback() in an async with block.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/index.html b/docs/zh/1.1b2/index.html new file mode 100644 index 0000000..da8f7b3 --- /dev/null +++ b/docs/zh/1.1b2/index.html @@ -0,0 +1,229 @@ + + + + + + + + 欢迎来到 GINO 的文档! - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    欢迎来到 GINO 的文档!

    +PyPI Release Version +GitHub Workflow Status for tests +Codacy coverage +Dependabot +

    GINO 递归定义为 GINO Is Not ORM,是一个基于 asyncioSQLAlchemy core 的轻量级异步 Python ORM 框架,目前(2020 年初)仅支持 asyncpg 一种引擎。

    + + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/objects.inv b/docs/zh/1.1b2/objects.inv new file mode 100644 index 0000000..6775e3a Binary files /dev/null and b/docs/zh/1.1b2/objects.inv differ diff --git a/docs/zh/1.1b2/py-modindex.html b/docs/zh/1.1b2/py-modindex.html new file mode 100644 index 0000000..18dcbf9 --- /dev/null +++ b/docs/zh/1.1b2/py-modindex.html @@ -0,0 +1 @@ +

    domainindex.html

    \ No newline at end of file diff --git a/docs/zh/1.1b2/reference.html b/docs/zh/1.1b2/reference.html new file mode 100644 index 0000000..aac3a25 --- /dev/null +++ b/docs/zh/1.1b2/reference.html @@ -0,0 +1,336 @@ + + + + + + + + 参考手册 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    参考手册

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api.html b/docs/zh/1.1b2/reference/api.html new file mode 100644 index 0000000..bccdd07 --- /dev/null +++ b/docs/zh/1.1b2/reference/api.html @@ -0,0 +1,242 @@ + + + + + + + + API 参考 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.aiocontextvars.html b/docs/zh/1.1b2/reference/api/gino.aiocontextvars.html new file mode 100644 index 0000000..6b66bf1 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.aiocontextvars.html @@ -0,0 +1,216 @@ + + + + + + + + gino.aiocontextvars module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.aiocontextvars module

    +
    +
    +gino.aiocontextvars.patch_asyncio()
    +

    Patches asyncio to support contextvars.

    +

    This is automatically called when gino is imported. If Python version is 3.7 +or greater, this function is a no-op.

    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.api.html b/docs/zh/1.1b2/reference/api/gino.api.html new file mode 100644 index 0000000..7fe2800 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.api.html @@ -0,0 +1,621 @@ + + + + + + + + gino.api module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.api module

    +
    +
    +class gino.api.Gino(bind=None, model_classes=None, query_ext=True, schema_ext=True, ext=True, **kwargs)
    +

    基类:sqlalchemy.sql.schema.MetaData

    +

    All-in-one API class of GINO, providing several shortcuts.

    +

    This class is a subclass of SQLAlchemy +MetaData, therefore its instances can be used +as a normal MetaData object, e.g. used in +Alembic. In usual cases, you would +want to define one global Gino instance, usually under the name +of db, representing the database used in your application.

    +

    You may define tables in the official way +SQLAlchemy core recommended, but more often in GINO we define model classes +with db.Model as their parent class to represent tables, for its +objective interface and CRUD operations. Please read 增删改查 +for more information.

    +

    For convenience, Gino instance delegated all properties publicly +exposed by sqlalchemy, so that you can define tables / models +without importing sqlalchemy:

    +
    id = db.Column(db.BigInteger(), primary_key=True)
    +
    +
    +

    Similar to MetaData, a Gino object +can bind to a GinoEngine instance, hereby allowing +"implicit execution" through the gino +extension on Executable or +SchemaItem constructs:

    +
    await User.query.gino.first()
    +await db.gino.create_all()
    +
    +
    +

    Differently, GINO encourages the use of implicit execution and manages +transactional context correctly.

    +

    Binding to a connection object is not supported.

    +

    To set a bind property, you can simply set your +GinoEngine object on db.bind, or +set it to None to unbind. However, the creation of engine usually +happens at the same time. Therefore, GINO provided several convenient ways +doing so:

    +
      +
    1. with_bind() returning an asynchronous context manager:

      +
      async with db.with_bind('postgresql://...') as engine:
      +
      +
      +
    2. +
    3. set_bind() and pop_bind():

      +
      engine = await db.set_bind('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    4. +
    5. Directly await on Gino instance:

      +
      db = await gino.Gino('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    6. +
    +
    +

    注解

    +

    SQLAlchemy allows creating the engine by:

    +
    metadata.bind = 'postgresql://...'
    +
    +
    +

    While in GINO this only sets a string to bind, because +creating an engine requires await, which is exactly what +set_bind() does.

    +
    +

    At last, Gino delegates all query APIs on the bound +GinoEngine.

    +
    +
    +property Model
    +

    Declarative base class for models, subclass of +gino.declarative.Model. Defining subclasses of this class will +result new tables added to this Gino metadata.

    +
    + +
    +
    +acquire(*args, **kwargs)
    +

    A delegate of GinoEngine.acquire().

    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.all().

    +
    + +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    A delegate of Bakery.bake().

    +
    +

    1.1 新版功能.

    +
    +
    + +
    +
    +property bakery
    +

    The bundled Bakery instance.

    +
    +

    1.1 新版功能.

    +
    +
    + +
    +
    +property bind
    +

    An GinoEngine to which this Gino is bound.

    +

    This is a simple property with no getter or setter hook - what you set +is what you get. To achieve the same result as it is in SQLAlchemy - +setting a string or URL and getting an +engine instance, use set_bind() (or await on this +Gino object after setting a string or +URL).

    +
    + +
    +
    +compile(elem, *multiparams, **params)
    +

    A delegate of GinoEngine.compile().

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.first().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.iterate().

    +
    + +
    +
    +model_base_classes = (<class 'gino.crud.CRUDModel'>,)
    +

    Overridable default model classes to build the Model.

    +

    Default is (CRUDModel, ).

    +
    + +
    +
    +no_delegate = {'create_engine', 'engine_from_config'}
    +

    A set of symbols from sqlalchemy which is not delegated by +Gino.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one_or_none().

    +
    + +
    +
    +pop_bind()
    +

    Unbind self, and return the bound engine.

    +

    This is usually used in a chained call to close the engine:

    +
    await db.pop_bind().close()
    +
    +
    +
    +
    返回
    +

    GinoEngine or None if self is not bound.

    +
    +
    +
    + +
    +
    +query_executor
    +

    The overridable gino extension class on +Executable.

    +

    This class will be set as the getter method of the property gino on +Executable and its subclasses, if +ext and query_ext arguments are both True. Default is +GinoExecutor.

    +

    GinoExecutor 的别名

    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.scalar().

    +
    + +
    +
    +schema_visitor
    +

    gino.schema.GinoSchemaVisitor 的别名

    +
    + +
    +
    +async set_bind(bind, loop=None, **kwargs)
    +

    Bind self to the given GinoEngine and return it.

    +

    If the given bind is a string or +URL, all arguments will be sent to +create_engine() to create a new engine, and return it.

    +
    +
    返回
    +

    GinoEngine

    +
    +
    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.status().

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    A delegate of GinoEngine.transaction().

    +
    + +
    +
    +with_bind(bind, loop=None, **kwargs)
    +

    Shortcut for set_bind() and pop_bind() plus closing engine.

    +

    This method accepts the same arguments of +create_engine(). This allows inline creating an engine and +binding self on enter, and unbinding self and closing the engine on +exit:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # play with engine
    +
    +
    +
    +
    返回
    +

    An asynchronous context manager.

    +
    +
    +
    + +
    + +
    +
    +class gino.api.GinoExecutor(query)
    +

    基类:object

    +

    The default gino extension on +Executable constructs for implicit +execution.

    +

    Instances of this class are created when visiting the gino property of +Executable instances (also referred as +queries or clause elements), for example:

    +
    await User.query.gino.first()
    +
    +
    +

    This allows GINO to add the asynchronous query APIs (all(), +first(), one(), one_or_none(), scalar(), +status(), iterate()) to SQLAlchemy query clauses without +messing up with existing synchronous ones. +Calling these asynchronous query APIs has the same restriction - the +relevant metadata (the Gino instance) must be bound to an engine, +or an AttributeError will be raised.

    +
    +

    注解

    +

    Executable clause elements that are completely irrelevant with any +table - for example db.select([db.text('now()')]) - has no +metadata, hence no engine. Therefore, this will always fail:

    +
    await db.select([db.text('now()')]).gino.scalar()
    +
    +
    +

    You should use conn.scalar(), +engine.scalar() or even +db.scalar() in this case.

    +
    +
    +
    +async all(*multiparams, **params)
    +

    Returns engine.all() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +execution_options(**options)
    +

    Set execution options to this query in a chaining call.

    +

    Read execution_options() for more +information.

    +
    +
    参数
    +

    options -- Multiple execution options.

    +
    +
    +
    +

    1.1 新版功能.

    +
    +
    + +
    +
    +async first(*multiparams, **params)
    +

    Returns engine.first() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +iterate(*multiparams, **params)
    +

    Returns engine.iterate() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +load(value)
    +

    Shortcut to set execution option loader in a chaining call.

    +

    For example to load Book instances with their authors:

    +
    query = Book.join(User).select()
    +books = await query.gino.load(Book.load(author=User)).all()
    +
    +
    +

    Read execution_options() for more +information.

    +
    + +
    +
    +model(model)
    +

    Shortcut to set execution option model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async one(*multiparams, **params)
    +

    Returns engine.one() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async one_or_none(*multiparams, **params)
    +

    Returns engine.one_or_none() +with this query as the first argument, and other arguments followed, +where engine is the GinoEngine to which the +metadata (Gino) is bound, while metadata is found in this +query.

    +
    + +
    +
    +property query
    +

    Get back the chained Executable.

    +

    In a chained query calls, occasionally the previous query clause is +needed after a .gino. chain, you can use .query. to resume the +chain back. For example:

    +
    await User.query.gino.model(FOUser).query.where(...).gino.all()
    +
    +
    +
    + +
    +
    +return_model(switch)
    +

    Shortcut to set execution option return_model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async scalar(*multiparams, **params)
    +

    Returns engine.scalar() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async status(*multiparams, **params)
    +

    Returns engine.status() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +timeout(timeout)
    +

    Shortcut to set execution option timeout in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.bakery.html b/docs/zh/1.1b2/reference/api/gino.bakery.html new file mode 100644 index 0000000..943f3c3 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.bakery.html @@ -0,0 +1,323 @@ + + + + + + + + gino.bakery module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.bakery module

    +
    +
    +class gino.bakery.BakedQuery(elem, metadata, hash_=None)
    +

    基类:gino.api.GinoExecutor

    +

    Represents a pre-compiled and possibly prepared query for faster execution.

    +

    BakedQuery is created by Bakery.bake(), and can be executed by +GinoEngine or GinoConnection. If there +is a proper bind in the baked query, contextual execution APIs inherited from +GinoExecutor can also be used.

    +
    +

    1.1 新版功能.

    +
    +
    +
    +property bind
    +

    Internal API to provide a proper bind if found.

    +
    + +
    +
    +property compiled_sql
    +

    Internal API to get the SQLAlchemy compiled sql context.

    +
    + +
    +
    +execution_options(**kwargs)
    +

    Set execution options on a shadow query of this baked query.

    +

    The execution options set in this method won't affect the execution options in +the baked query.

    +

    Read execution_options() for more +information.

    +
    +
    参数
    +

    options -- Multiple execution options.

    +
    +
    返回
    +

    A shadow of the baked query with new execution options but still +functions as a baked query.

    +
    +
    +
    + +
    +
    +get(_)
    +

    Internal API to get the compiled_sql.

    +
    +
    参数
    +

    _ -- Ignored.

    +
    +
    +
    + +
    +
    +property query
    +

    Internal API to get the query instance before compilation.

    +
    + +
    +
    +property sql
    +

    Internal API to get the compiled raw SQL.

    +
    + +
    + +
    +
    +class gino.bakery.Bakery
    +

    基类:object

    +

    Factory and warehouse of baked queries.

    +

    You may provide a bakery to a GinoEngine during creation as +the bakery keyword argument, and the engine will bake the queries and create +corresponding prepared statements for each of the connections in the pool.

    +

    A Gino instance has a built-in bakery, +it's automatically given to the engine during set_bind() or +with_bind().

    +
    +

    1.1 新版功能.

    +
    +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    Bake a query.

    +

    You can bake raw SQL strings or SQLAlchemy Core query instances. This method +adds the given query into a queue in the bakery, and bakes it only when the +bakery is set to an GinoEngine from which the bakery could +learn about the SQL dialect and compile the queries into SQL. Once done, the +bakery is "closed", you can neither give it to another engine, nor use it to +bake more queries.

    +
    +
    参数
    +
      +
    • func_or_elem -- A str or a SQLAlchemy Core query instance, or a +function that returns such results.

    • +
    • execution_options -- Shortcut to add SQLAlchemy execution options to the +query.

    • +
    +
    +
    返回
    +

    A BakedQuery instance.

    +
    +
    +
    + +
    +
    +query_cls
    +

    BakedQuery 的别名

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.crud.html b/docs/zh/1.1b2/reference/api/gino.crud.html new file mode 100644 index 0000000..9ca7425 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.crud.html @@ -0,0 +1,653 @@ + + + + + + + + gino.crud module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.crud module

    +
    +
    +class gino.crud.Alias(model, *args, **kwargs)
    +

    基类:object

    +

    Experimental proxy for table alias on model.

    +
    +
    +distinct(*columns)
    +
    + +
    +
    +load(*column_names, **relationships)
    +
    + +
    +
    +on(on_clause)
    +
    + +
    + +
    +
    +class gino.crud.CRUDModel(**values)
    +

    基类:gino.declarative.Model

    +

    The base class for models with CRUD support.

    +

    Don't inherit from this class directly, because it has no metadata. Use +db.Model instead.

    +
    +
    +classmethod alias(*args, **kwargs)
    +

    Experimental proxy for table alias on model.

    +
    + +
    +
    +append_where_primary_key(q)
    +

    Append where clause to locate this model instance by primary on the +given query, and return the new query.

    +

    This is mostly used internally in GINO, but also available for such +usage:

    +
    await user.append_where_primary_key(User.query).gino.first()
    +
    +
    +

    which is identical to:

    +
    await user.query.gino.first()
    +
    +
    +
    +

    0.7.6 版后已移除: Use lookup() instead.

    +
    +
    + +
    +
    +create(bind=None, timeout=<object object>, **values)
    +

    This create behaves a bit different on model classes compared to model +instances.

    +

    On model classes, create will create a new model instance and insert +it into database. On model instances, create will just insert the +instance into the database.

    +

    Under the hood create() uses INSERT ... RETURNING ... to +create the new model instance and load it with database default data if +not specified.

    +

    Some examples:

    +
    user1 = await User.create(name='fantix', age=32)
    +user2 = User(name='gino', age=42)
    +await user2.create()
    +
    +
    +
    +
    参数
    +
      +
    • bind -- A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    • values -- Keyword arguments are pairs of attribute names and their +initial values. Only available when called on a model class.

    • +
    +
    +
    返回
    +

    The instance of this model class (newly created or existing).

    +
    +
    +
    + +
    +
    +delete
    +

    Similar to update(), this delete is also different on model +classes than on model instances.

    +

    On model classes delete is an attribute of type +Delete for massive deletes, for +example:

    +
    await User.delete.where(User.enabled.is_(False)).gino.status()
    +
    +
    +

    Similarly you can add a returning() +clause to the query and it shall return the deleted rows as model objects.

    +

    And on model instances, delete() is a method to remove the +corresponding row in the database of this model instance. and returns the +status returned from the database:

    +
    print(await user.delete())  # e.g. prints DELETE 1
    +
    +
    +
    +

    注解

    +

    delete() only removes the row from database, it does not affect the +current model instance.

    +
    +
    +
    参数
    +
      +
    • bind -- An optional GinoEngine if current +metadata (Gino) has no bound engine, or specifying a +different GinoEngine to execute the DELETE.

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    +
    + +
    +
    +classmethod distinct(*columns)
    +

    Experimental loader feature to yield only distinct instances by given +columns.

    +
    + +
    +
    +async classmethod get(ident, bind=None, timeout=<object object>)
    +

    Get an instance of this model class by primary key.

    +

    For example:

    +
    user = await User.get(request.args.get('user_id'))
    +
    +
    +
    +
    参数
    +
      +
    • ident -- Value of the primary key. For composite primary keys this +should be a tuple of values for all keys in database order, or a dict +of names (or position numbers in database order starting from zero) +of all primary keys to their values.

    • +
    • bind -- A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    返回
    +

    An instance of this model class, or None if no such row.

    +
    +
    +
    + +
    +
    +classmethod in_query(query)
    +

    Convenient method to get a Model object when using subqueries.

    +

    Though with filters and aggregations, subqueries often return same +columns as the original table, but SQLAlchemy could not recognize them +as the columns are in subqueries, so technically they're columns in the +new "table".

    +

    With this method, the columns are loaded into the original models when +being used in subqueries. For example:

    +
    query = query.alias('users')
    +MyUser = User.in_query(query)
    +
    +loader = MyUser.distinct(User1.id).load()
    +users = await query.gino.load(loader).all()
    +
    +
    +
    + +
    +
    +classmethod load(*column_names, **relationships)
    +

    Populates a loader.Loader instance to be used by the +loader execution option in order to customize the loading behavior +to load specified fields into instances of this model.

    +

    The basic usage of this method is to provide the loader execution +option (if you are looking for reloading +the instance from database, check get() or query) for a +given query.

    +

    This method takes both positional arguments and keyword arguments with +very different meanings. The positional arguments should be column +names as strings, specifying only these columns should be loaded into +the model instance (other values are discarded even if they are +retrieved from database). Meanwhile, the keyword arguments should be +loaders for instance attributes. For example:

    +
    u = await User.query.gino.load(User.load('id', 'name')).first()
    +
    +
    +
    +

    小技巧

    +

    gino.load is a shortcut for setting the execution option +loader.

    +
    +

    This will populate a User instance with only id and name +values, all the rest are simply None even if the query actually +returned all the column values.

    +
    q = User.join(Team).select()
    +u = await q.gino.load(User.load(team=Team)).first()
    +
    +
    +

    This will load two instances of model User and Team, returning +the User instance with u.team set to the Team instance.

    +

    Both positional and keyword arguments can be used ath the same time. If +they are both omitted, like Team.load(), it is equivalent to just +Team as a loader.

    +

    Additionally, a loader.Loader instance can also be used to +generate queries, as its structure is usually the same as the query:

    +
    u = await User.load(team=Team).query.gino.first()
    +
    +
    +

    This generates a query like this:

    +
    SELECT users.xxx, ..., teams.xxx, ...
    +  FROM users LEFT JOIN teams
    +    ON ...
    +
    +
    +

    The Loader delegates attributes on the query, so +.query can be omitted. The LEFT JOIN is built-in behavior, +while the ON clause is generated based on foreign key. If there is +no foreign key, or the condition should be customized, you can use +this:

    +
    u = await User.load(
    +    team=Team.on(User.team_id == Team.id)).gino.first()
    +
    +
    +

    And you can use both load() and on() at the same time +in a chain, in whatever order suits you.

    + +
    + +
    +
    +lookup()
    +

    Generate where-clause expression to locate this model instance.

    +

    By default this method uses current values of all primary keys, and you +can override it to behave differently. Most instance-level CRUD +operations depend on this method internally. Particularly while +lookup() is called in update(), the where condition is +used in UpdateRequest.apply(), so that queries like UPDATE ... +SET id = NEW WHERE id = OLD could work correctly.

    +
    +
    返回
    +

    +
    +
    +
    +

    0.7.6 新版功能.

    +
    +
    + +
    +
    +classmethod none_as_none(enabled=True)
    +
    + +
    +
    +classmethod on(on_clause)
    +

    Customize the on-clause for the auto-generated outer join query.

    +
    +

    注解

    +

    This has no effect when provided as the loader execution option +for a given query.

    +
    +
    +

    参见

    +

    load()

    +
    +
    + +
    +
    +query
    +

    Get a SQLAlchemy query clause of the table behind this model. This equals +to sqlalchemy.select([self.__table__]). If this attribute is retrieved on a +model instance, then a where clause to locate this instance by its primary +key is appended to the returning query clause. This model type is set as +the execution option model in the returning clause, so by default the +query yields instances of this model instead of database rows.

    +
    + +
    +
    +select()
    +

    Build a query to retrieve only specified columns from this table.

    +

    This method accepts positional string arguments as names of attributes to +retrieve, and returns a Select for +query. The returning query object is always set with two execution options:

    +
      +
    1. model is set to this model type

    2. +
    3. return_model is set to False

    4. +
    +

    So that by default it always return rows instead of model instances, while +column types can be inferred correctly by the model option.

    +

    For example:

    +
    async for row in User.select('id', 'name').gino.iterate():
    +    print(row['id'], row['name'])
    +
    +
    +

    If select() is invoked on a model instance, then a WHERE clause +to locate this instance by its primary key is appended to the returning +query clause. This is useful when you want to retrieve a latest value of a +field on current model instance from database:

    +
    db_age = await user.select('age').gino.scalar()
    +
    +
    + +
    + +
    +
    +to_dict()
    +

    Convenient method to generate a dict from this model instance.

    +

    Keys will be attribute names, while values are loaded from memory (not +from database). If there are JSONProperty +attributes in this model, their source JSON field will not be included +in the returning dict - instead the JSON attributes will be.

    +
    +

    参见

    +

    json_support

    +
    +
    + +
    +
    +update
    +

    This update behaves quite different on model classes rather than model +instances.

    +

    On model classes, update is an attribute of type +Update for massive updates, for +example:

    +
    await User.update.values(enabled=True).where(...).gino.status()
    +
    +
    +

    Like query, the update query also has the model execution +option of this model, so if you use the +returning() clause, the query shall +return model objects.

    +

    However on model instances, update() is a method which accepts keyword +arguments only and returns an UpdateRequest to update this single +model instance. The keyword arguments are pairs of attribute names and new +values. This is the same as UpdateRequest.update(), feel free to +read more about it. A normal usage example would be like this:

    +
    await user.update(name='new name', age=32).apply()
    +
    +
    +

    Here, the await ... apply() executes the +actual UPDATE SQL in the database, while user.update() only makes +changes in the memory, and collect all changes into an +UpdateRequest instance.

    +
    + +
    + +
    +
    +class gino.crud.QueryModel
    +

    基类:type

    +

    Metaclass of Model classes used for subqueries.

    +
    + +
    +
    +class gino.crud.UpdateRequest(instance: gino.crud.CRUDModel)
    +

    基类:object

    +

    A collection of attributes and their new values to update on one model +instance.

    +

    UpdateRequest instances are created by CRUDModel.update, +don't instantiate manually unless required. Every UpdateRequest +instance is bound to one model instance, all updates are for that one +specific model instance and its database row.

    +
    +
    +async apply(bind=None, timeout=<object object>)
    +

    Apply pending updates into database by executing an UPDATE SQL.

    +
    +
    参数
    +
      +
    • bind -- A GinoEngine to execute the SQL, or +None (default) to use the bound engine in the metadata.

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    返回
    +

    self for chaining calls.

    +
    +
    +
    + +
    +
    +update(**values)
    +

    Set given attributes on the bound model instance, and add them into +the update collections for apply().

    +

    Given keyword-only arguments are pairs of attribute names and values to +update. This is not a coroutine, calling update() will have +instant effect on the bound model instance - its in-memory values will +be updated immediately. Therefore this can be used individually as a +shortcut to update several attributes in a batch:

    +
    user.update(age=32, disabled=True)
    +
    +
    +

    update() returns self for chaining calls to either +apply() or another update(). If one attribute is updated +several times by the same UpdateRequest, then only the last +value is remembered for apply().

    +

    Updated values can be SQLAlchemy expressions, for example an atomic +increment for user balance looks like this:

    +
    await user.update(balance=User.balance + 100).apply()
    +
    +
    +
    +

    注解

    +

    Expression values will not affect the in-memory attribute value on +update() before apply(), because it has no knowledge +of the latest value in the database. After apply() the new +value will be automatically reloaded from database with +RETURNING clause.

    +
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.declarative.html b/docs/zh/1.1b2/reference/api/gino.declarative.html new file mode 100644 index 0000000..dade310 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.declarative.html @@ -0,0 +1,401 @@ + + + + + + + + gino.declarative module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.declarative module

    +
    +
    +class gino.declarative.ColumnAttribute(prop_name, column)
    +

    基类:object

    +

    The type of the column wrapper attributes on GINO models.

    +

    This is the core utility to enable GINO models so that:

    +
      +
    • Accessing a column attribute on a model class returns the column itself

    • +
    • Accessing a column attribute on a model instance returns the value for that column

    • +
    +

    This utility is customizable by defining __attr_factory__ in the model class.

    +
    + +
    +
    +class gino.declarative.InvertDict(*args, **kwargs)
    +

    基类:dict

    +

    A custom dict that allows getting keys by values.

    +

    Used internally by Model.

    +
    +
    +invert_get(value, default=None)
    +

    Get key by value.

    +
    +
    参数
    +
      +
    • value -- A value in this dict.

    • +
    • default -- If specified value doesn't exist, return default.

    • +
    +
    +
    返回
    +

    The corresponding key if the value is found, or default otherwise.

    +
    +
    +
    + +
    + +
    +
    +class gino.declarative.Model
    +

    基类:object

    +

    The base class of GINO models.

    +

    This is not supposed to be sub-classed directly, declarative_base() should +be used instead to generate a base model class with a given +MetaData. By defining subclasses of a model, instances +of sqlalchemy.schema.Table will be created and added to the bound +MetaData. The columns of the +Table instance are defined as +Column attributes:

    +
    from sqlalchemy import MetaData, Column, Integer, String
    +from gino.declarative import declarative_base
    +
    +Model = declarative_base()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = Column(Integer(), primary_key=True)
    +    name = Column(String())
    +
    +
    +

    The name of the columns are automatically set using the attribute name.

    +

    An instance of a model will maintain a memory storage for values of all the defined +column attributes. You can access these values by the same attribute name, or update +with new values, just like normal Python objects:

    +
    u = User()
    +assert u.name is None
    +
    +u.name = "daisy"
    +assert u.name == "daisy"
    +
    +
    +
    +

    注解

    +

    Accessing column attributes on a model instance will NOT trigger any database +operation.

    +
    +

    Constraint and Index are +also allowed as model class attributes. Their attribute names are not used.

    +

    A concrete model class can be used as a replacement of the +Table it reflects in SQLAlchemy queries. The model class +is also iterable, yielding all the Column instances +defined in the model.

    +

    Other built-in class attributes:

    +
      +
    • __metadata__

      +

      This is supposed to be set by declarative_base() and used only during +subclass construction. Still, this can be treated as a read-only attribute to +find out which MetaData this model is bound to.

      +
    • +
    • __tablename__

      +

      This is a required attribute to define a concrete model, meaning a +sqlalchemy.schema.Table instance will be created, added to the bound +MetaData and set to the class attribute __table__. +Not defining __tablename__ will result in an abstract model - no table +instance will be created, and instances of an abstract model are meaningless.

      +
    • +
    • __table__

      +

      This should usually be treated as an auto-generated read-only attribute storing +the sqlalchemy.schema.Table instance.

      +
    • +
    • __attr_factory__

      +

      An attribute factory that is used to wrap the actual +Column instance on the model class, so that the access +to the column attributes on model instances is redirected to the in-memory value +store. The default factory is ColumnAttribute, can be override.

      +
    • +
    • __values__

      +

      The internal in-memory value store as a dict, only available on model +instances. Accessing column attributes is equivalent to accessing __values__.

      +
    • +
    +
    + +
    +
    +gino.declarative.declarative_base(metadata, model_classes=(<class 'gino.declarative.Model'>, ), name='Model')
    +

    Create a base GINO model class for declarative table definition.

    +
    +
    参数
    +
      +
    • metadata -- A MetaData instance to contain the +tables.

    • +
    • model_classes -- Base class(es) of the base model class to be created. Default: +Model.

    • +
    • name -- The class name of the base model class to be created. Default: +Model.

    • +
    +
    +
    返回
    +

    A new base model class.

    +
    +
    +
    + +
    +
    +gino.declarative.declared_attr(m=None, *, with_table=False)
    +

    Mark a class-level method as a factory of attribute.

    +

    This is intended to be used as decorators on class-level methods of a +Model class. When initializing the class as well as its +subclasses, the decorated factory method will be called for each class, the +returned result will be set on the class in place of the factory method +under the same name.

    +

    @declared_attr is implemented differently than +declared_attr of SQLAlchemy, but they +are both more often used on mixins to dynamically declare indices or +constraints (also works for column and __table_args__, or even normal +class attributes):

    +
    class TrackedMixin:
    +    created = db.Column(db.DateTime(timezone=True))
    +
    +    @db.declared_attr
    +    def unique_id(cls):
    +        return db.Column(db.Integer())
    +
    +    @db.declared_attr
    +    def unique_constraint(cls):
    +        return db.UniqueConstraint('unique_id')
    +
    +    @db.declared_attr
    +    def poly(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.Column(db.Unicode())
    +
    +    @db.declared_attr
    +    def __table_args__(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.UniqueConstraint('poly'),
    +
    +
    +
    +

    注解

    +

    This doesn't work if the model already had a __table__.

    +
    +
    +

    在 1.1 版更改: Added with_table parameter which works after the __table__ is created:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    ...
    +
    +    @db.declared_attr(with_table=True)
    +    def table_name(cls):
    +        # this is called only once when defining the class
    +        return cls.__table__.name
    +
    +assert User.table_name == "users"
    +
    +
    +
    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.dialects.aiomysql.html b/docs/zh/1.1b2/reference/api/gino.dialects.aiomysql.html new file mode 100644 index 0000000..c348be1 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.dialects.aiomysql.html @@ -0,0 +1,588 @@ + + + + + + + + gino.dialects.aiomysql module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.aiomysql module

    +
    +
    +class gino.dialects.aiomysql.AiomysqlDBAPI
    +

    基类:gino.dialects.base.BaseDBAPI

    +
    +
    +paramstyle = 'format'
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlDialect(*args, bakery=None, **kwargs)
    +

    基类:sqlalchemy.dialects.mysql.base.MySQLDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.dialects.mysql.types._IntegerType'>: <class 'sqlalchemy.dialects.mysql.types._IntegerType'>, <class 'sqlalchemy.dialects.mysql.types._NumericType'>: <class 'sqlalchemy.dialects.mysql.types._NumericType'>, <class 'sqlalchemy.dialects.mysql.types._FloatType'>: <class 'sqlalchemy.dialects.mysql.types._FloatType'>, <class 'sqlalchemy.sql.sqltypes.Numeric'>: <class 'sqlalchemy.dialects.mysql.types.NUMERIC'>, <class 'sqlalchemy.sql.sqltypes.Float'>: <class 'sqlalchemy.dialects.mysql.types.FLOAT'>, <class 'sqlalchemy.sql.sqltypes.Time'>: <class 'sqlalchemy.dialects.mysql.types.TIME'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.MatchType'>: <class 'sqlalchemy.dialects.mysql.types._MatchType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.mysql.json.JSON'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONIndexType'>: <class 'sqlalchemy.dialects.mysql.json.JSONIndexType'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'sqlalchemy.dialects.mysql.json.JSONPathType'>, <class 'sqlalchemy.dialects.mysql.enumerated.ENUM'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.aiomysql.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    DBAPICursor 的别名

    +
    + +
    +
    +dbapi_class
    +

    AiomysqlDBAPI 的别名

    +
    + +
    +
    +driver = 'aiomysql'
    +
    + +
    +
    +execution_ctx_cls
    +

    AiomysqlExecutionContext 的别名

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given a DBAPI connection, return its isolation level.

    +

    When working with a _engine.Connection object, +the corresponding +DBAPI connection may be procured using the +_engine.Connection.connection accessor.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine isolation level facilities; +these APIs should be preferred for most typical use cases.

    +
    +

    参见

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +init_kwargs = {'auth_plugin', 'autocommit', 'bakery', 'charset', 'client_flag', 'connect_timeout', 'conv', 'cursorclass', 'db', 'host', 'init_command', 'local_infile', 'loop', 'maxsize', 'minsize', 'no_delay', 'password', 'pool_recycle', 'port', 'prebake', 'program_name', 'read_default_file', 'read_default_group', 'server_public_key', 'sql_mode', 'ssl', 'unix_socket', 'use_unicode', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument "conn" which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The "do_on_connect" callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    返回
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    参见

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +postfetch_lastrowid = False
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given a DBAPI connection, set its isolation level.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine +isolation level facilities; these APIs should be preferred for +most typical use cases.

    +
    +

    参见

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +statement_compiler
    +

    sqlalchemy.dialects.mysql.base.MySQLCompiler 的别名

    +
    + +
    +
    +support_prepare = False
    +
    + +
    +
    +support_returning = False
    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlExecutionContext
    +

    基类:gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.mysql.base.MySQLExecutionContext

    +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +

    return self.cursor.lastrowid, or equivalent, after an INSERT.

    +

    This may involve calling special cursor functions, +issuing a new SELECT on the cursor (or a new one), +or returning a stored value that was +calculated within post_exec().

    +

    This function will only be called for dialects +which support "implicit" primary key generation, +keep preexecute_autoincrement_sequences set to False, +and when no explicit id value was bound to the +statement.

    +

    The function is called once, directly after +post_exec() and before the transaction is committed +or ResultProxy is generated. If the post_exec() +method assigns a value to self._lastrowid, the +value is used in place of calling get_lastrowid().

    +

    Note that this method is not equivalent to the +lastrowid method on ResultProxy, which is a +direct proxy to the DBAPI lastrowid accessor +in all cases.

    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlIterator(context, cursor)
    +

    基类:gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AsyncEnum(*enums, **kw)
    +

    基类:sqlalchemy.dialects.mysql.enumerated.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.DBAPICursor(dbapi_conn)
    +

    基类:gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +iterate(context)
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.GinoNullType
    +

    基类:sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +
      +
    • dialect -- Dialect instance in use.

    • +
    • coltype -- DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Pool(url, loop, init=None, bakery=None, prebake=True, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Transaction(conn, set_isolation=None)
    +

    基类:gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.dialects.asyncpg.html b/docs/zh/1.1b2/reference/api/gino.dialects.asyncpg.html new file mode 100644 index 0000000..6eafd96 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.dialects.asyncpg.html @@ -0,0 +1,598 @@ + + + + + + + + gino.dialects.asyncpg module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.asyncpg module

    +
    +
    +class gino.dialects.asyncpg.AsyncEnum(*enums, **kw)
    +

    基类:sqlalchemy.dialects.postgresql.base.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCompiler(dialect, statement, column_keys=None, inline=False, **kwargs)
    +

    基类:sqlalchemy.dialects.postgresql.base.PGCompiler

    +
    +
    +property bindtemplate
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCursor(context, cursor)
    +

    基类:gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDBAPI
    +

    基类:gino.dialects.base.BaseDBAPI

    +
    +
    +Error = (<class 'asyncpg.exceptions._base.PostgresError'>, <class 'asyncpg.exceptions._base.InterfaceError'>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDialect(*args, bakery=None, **kwargs)
    +

    基类:sqlalchemy.dialects.postgresql.base.PGDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.sql.sqltypes.ARRAY'>: <class 'sqlalchemy.dialects.postgresql.array.ARRAY'>, <class 'sqlalchemy.sql.sqltypes.Interval'>: <class 'sqlalchemy.dialects.postgresql.base.INTERVAL'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'gino.dialects.asyncpg.AsyncpgJSONPathType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.postgresql.json.JSON'>, <class 'sqlalchemy.dialects.postgresql.base.ENUM'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.asyncpg.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    DBAPICursor 的别名

    +
    + +
    +
    +dbapi_class
    +

    AsyncpgDBAPI 的别名

    +
    + +
    +
    +driver = 'asyncpg'
    +
    + +
    +
    +execution_ctx_cls
    +

    AsyncpgExecutionContext 的别名

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given an asyncpg connection, return its isolation level.

    +
    + +
    +
    +async has_schema(connection, schema)
    +
    + +
    +
    +async has_sequence(connection, sequence_name, schema=None)
    +

    Check the existence of a particular sequence in the database.

    +

    Given a _engine.Connection object and a string +sequence_name, return True if the given sequence exists in +the database, False otherwise.

    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +async has_type(connection, type_name, schema=None)
    +
    + +
    +
    +init_kwargs = {'bakery', 'command_timeout', 'connection_class', 'database', 'host', 'init', 'loop', 'max_cacheable_statement_size', 'max_cached_statement_lifetime', 'max_inactive_connection_lifetime', 'max_queries', 'max_size', 'min_size', 'passfile', 'password', 'port', 'prebake', 'server_settings', 'setup', 'ssl', 'statement_cache_size', 'timeout', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument "conn" which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The "do_on_connect" callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    返回
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    参见

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given an asyncpg connection, set its isolation level.

    +
    + +
    +
    +statement_compiler
    +

    AsyncpgCompiler 的别名

    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgExecutionContext
    +

    基类:gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.postgresql.base.PGExecutionContext

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgIterator(context, iterator)
    +

    基类:object

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgJSONPathType
    +

    基类:sqlalchemy.dialects.postgresql.json.JSONPathType

    +
    +
    +bind_processor(dialect)
    +

    Return a conversion function for processing bind values.

    +

    Returns a callable which will receive a bind parameter value +as the sole positional argument and will return a value to +send to the DB-API.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +

    dialect -- Dialect instance in use.

    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.DBAPICursor(dbapi_conn)
    +

    基类:gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.GinoNullType
    +

    基类:sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +
      +
    • dialect -- Dialect instance in use.

    • +
    • coltype -- DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.NullPool(url, loop, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.Pool(url, loop, bakery=None, prebake=True, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.PreparedStatement(prepared, clause=None)
    +

    基类:gino.dialects.base.PreparedStatement

    +
    + +
    +
    +class gino.dialects.asyncpg.Transaction(tx)
    +

    基类:gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.dialects.base.html b/docs/zh/1.1b2/reference/api/gino.dialects.base.html new file mode 100644 index 0000000..f2afffd --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.dialects.base.html @@ -0,0 +1,482 @@ + + + + + + + + gino.dialects.base module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.base module

    +
    +
    +class gino.dialects.base.AsyncDialectMixin
    +

    基类:object

    +
    +
    +compile(elem, *multiparams, **params)
    +
    + +
    +
    +cursor_cls
    +

    DBAPICursor 的别名

    +
    + +
    +
    +classmethod dbapi()
    +
    + +
    +
    +dbapi_class
    +

    BaseDBAPI 的别名

    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +support_prepare = True
    +
    + +
    +
    +support_returning = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.base.BaseDBAPI
    +

    基类:object

    +
    +
    +static Binary(x)
    +
    + +
    +
    +Error
    +

    builtins.Exception 的别名

    +
    + +
    +
    +paramstyle = 'numeric'
    +
    + +
    + +
    +
    +class gino.dialects.base.Cursor
    +

    基类:object

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.base.DBAPICursor
    +

    基类:object

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +execute(statement, parameters)
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +executemany(statement, parameters)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.base.ExecutionContextOverride
    +

    基类:object

    +
    +
    +baked_query = None
    +
    + +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +
    + +
    +
    +get_result_proxy()
    +
    + +
    +
    +loader
    +
    + +
    +
    +model
    +
    + +
    +
    +process_rows(rows, return_model=True)
    +
    + +
    +
    +return_model
    +
    + +
    +
    +timeout
    +
    + +
    + +
    +
    +class gino.dialects.base.Pool
    +

    基类:object

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.base.PreparedStatement(clause=None)
    +

    基类:object

    +
    +
    +async all(*multiparams, **params)
    +
    + +
    +
    +async first(*multiparams, **params)
    +
    + +
    +
    +iterate(*params, **kwargs)
    +
    + +
    +
    +async scalar(*multiparams, **params)
    +
    + +
    +
    +async status(*multiparams, **params)
    +
    + +
    + +
    +
    +class gino.dialects.base.Transaction
    +

    基类:object

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.dialects.html b/docs/zh/1.1b2/reference/api/gino.dialects.html new file mode 100644 index 0000000..25e619a --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.dialects.html @@ -0,0 +1,227 @@ + + + + + + + + gino.dialects package - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects package

    + +
    +

    Module contents

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.engine.html b/docs/zh/1.1b2/reference/api/gino.engine.html new file mode 100644 index 0000000..1a7e0e6 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.engine.html @@ -0,0 +1,718 @@ + + + + + + + + gino.engine module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.engine module

    +
    +
    +class gino.engine.GinoConnection(dialect, sa_conn, stack=None)
    +

    基类:object

    +

    Represents an actual database connection.

    +

    This is the root of all query API like all(), first(), +one(), one_or_none(), scalar() or status(), +those on engine or query are simply wrappers of methods in this class.

    +

    Usually instances of this class are created by GinoEngine.acquire().

    +
    +

    注解

    +

    GinoConnection may refer to zero or one underlying database +connection - when a GinoConnection is acquired with +lazy=True, the underlying connection may still be in the pool, +until a query API is called or get_raw_connection() is called.

    +

    Oppositely, one underlying database connection can be shared by many +GinoConnection instances when they are acquired with +reuse=True. The actual database connection is only returned to the +pool when the root GinoConnection is released. Read more +in GinoEngine.acquire() method.

    +
    +
    +

    参见

    +

    引擎与连接

    +
    +
    +
    +async all(clause, *multiparams, **params)
    +

    Runs the given query in database, returns all results as a list.

    +

    This method accepts the same parameters taken by SQLAlchemy +execute(). You can pass in a raw +SQL string, or any SQLAlchemy query clauses.

    +

    If the given query clause is built by CRUD models, then the returning +rows will be turned into relevant model objects (Only one type of model +per query is supported for now, no relationship support yet). See +execution_options() for more information.

    +

    If the given parameters are parsed as "executemany" - bulk inserting +multiple rows in one call for example, the returning result from +database will be discarded and this method will return None.

    +
    + +
    +
    +property dialect
    +

    The Dialect in use, inherited +from the engine created this connection.

    +
    + +
    +
    +execution_options(**opt)
    +

    Set non-SQL options for the connection which take effect during +execution.

    +

    This method returns a copy of this GinoConnection which +references the same underlying database connection, but with the given +execution options set on the copy. Therefore, it is a good practice to +discard the copy immediately after use, for example:

    +
    row = await conn.execution_options(model=None).first(User.query)
    +
    +
    +

    This is very much the same as SQLAlchemy +execution_options(), it +actually does pass the execution options to the underlying SQLAlchemy +Connection. Furthermore, GINO added a +few execution options:

    +
    +
    参数
    +
      +
    • return_model -- Boolean to control whether the returning results +should be loaded into model instances, where the model class is +defined in another execution option model. Default is True.

    • +
    • model -- Specifies the type of model instance to create on return. +This has no effect if return_model is set to False. Usually +in queries built by CRUD models, this execution option is +automatically set. For now, GINO only supports loading each row into +one type of model object, relationships are not supported. Please use +multiple queries for that. None for no postprocessing (default).

    • +
    • timeout -- Seconds to wait for the query to finish. None for +no time out (default).

    • +
    • loader --

      A loader expression to load the database rows into +specified objective structure. It can be either:

      +
        +
      • A model class, so that the query will yield model instances of this +class. It is your responsibility to make sure all the columns of +this model is selected in the query.

      • +
      • A Column instance, so that each result +will be only a single value of this column. Please note, if you +want to achieve fetching the very first value, you should use +first() instead of +scalar(). However, using directly +scalar() is a more direct way.

      • +
      • A tuple nesting more loader expressions recursively.

      • +
      • A callable() function that will be called for each row to +fully customize the result. Two positional arguments will be passed +to the function: the first is the row instance, the second is a context +object which is only present if nested else None.

      • +
      • A Loader instance directly.

      • +
      • Anything else will be treated as literal values thus returned as +whatever they are.

      • +
      +

    • +
    +
    +
    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +async get_raw_connection(*, timeout=None)
    +

    Get the underlying database connection, acquire one if none present.

    +
    +
    参数
    +

    timeout -- Seconds to wait for the underlying acquiring

    +
    +
    返回
    +

    Underlying database connection instance depending on the +dialect in use

    +
    +
    Raises
    +

    TimeoutError if the acquiring timed out

    +
    +
    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    Cursors must work within transactions:

    +
    async with conn.transaction():
    +    async for user in conn.iterate(User.query):
    +        # handle each user without loading all users into memory
    +
    +
    +

    Alternatively, you can manually control how the cursor works:

    +
    async with conn.transaction():
    +    cursor = await conn.iterate(User.query)
    +    user = await cursor.next()
    +    users = await cursor.many(10)
    +
    +
    +

    Read more about how Cursor works.

    +

    Similarly, this method takes the same parameters as all().

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs the given query in database, returns exactly one result.

    +

    If the query returns no result, this method will raise +NoResultFound. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs the given query in database, returns at most one result.

    +

    If the query returns no result, this method will return None. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async prepare(clause)
    +
    + +
    +
    +property raw_connection
    +

    The current underlying database connection instance, type depends on +the dialect in use. May be None if self is a lazy connection.

    +
    + +
    +
    +async release(*, permanent=True)
    +

    Returns the underlying database connection to its pool.

    +

    If permanent=False, this connection will be set in lazy mode with +underlying database connection returned, the next query on this +connection will cause a new database connection acquired. This is +useful when this connection may still be useful again later, while some +long-running I/O operations are about to take place, which should not +take up one database connection or even transaction for that long time.

    +

    Otherwise with permanent=True (default), this connection will be +marked as closed after returning to pool, and be no longer usable +again.

    +

    If this connection is a reusing connection, then only this connection +is closed (depending on permanent), the reused underlying +connection will not be returned back to the pool.

    +

    Practically it is recommended to return connections in the reversed +order as they are borrowed, but if this connection is a reused +connection with still other opening connections reusing it, then on +release the underlying connection will be returned to the pool, +with all the reusing connections losing an available underlying +connection. The availability of further operations on those reusing +connections depends on the given permanent value.

    + +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +schema_for_object = <sqlalchemy.sql.schema._SchemaTranslateMap object>
    +

    A SQLAlchemy compatibility attribute, don't use it for now, it bites.

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the query status.

    +

    The returning query status depends on underlying database and the +dialect in use. For asyncpg it is a string, you can parse it like this: +https://git.io/v7oze

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    Starts a database transaction.

    +

    There are two ways using this method: managed as an asynchronous +context manager:

    +
    async with conn.transaction() as tx:
    +    # run query in transaction
    +
    +
    +

    or manually awaited:

    +
    tx = await conn.transaction()
    +try:
    +    # run query in transaction
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    Where the tx is an instance of the +GinoTransaction class, feel free to read +more about it.

    +

    In the first managed mode, the transaction is automatically committed +on exiting the context block, or rolled back if an exception was raised +which led to the exit of the context. In the second manual mode, you'll +need to manually call the +commit() or +rollback() methods on need.

    +

    If this is a lazy connection, entering a transaction will cause a new +database connection acquired if none was present.

    +

    Transactions may support nesting depending on the dialect in use. For +example in asyncpg, starting a second transaction on the same +connection will create a save point in the database.

    +

    For now, the parameters are directly passed to underlying database +driver, read asyncpg.connection.Connection.transaction() for +asyncpg.

    +
    + +
    + +
    +
    +class gino.engine.GinoEngine(dialect, pool, loop, logging_name=None, echo=None, execution_options=None)
    +

    基类:object

    +

    Connects a Pool and +Dialect together to provide a source +of database connectivity and behavior.

    +

    A GinoEngine object is instantiated publicly using the +gino.create_engine() function or +db.set_bind() method.

    +
    +

    参见

    +

    引擎与连接

    +
    +
    +
    +acquire(*, timeout=None, reuse=False, lazy=False, reusable=True)
    +

    Acquire a connection from the pool.

    +

    There are two ways using this method - as an asynchronous context +manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    which will guarantee the connection is returned to the pool when +leaving the async with block; or as a coroutine:

    +
    conn = await engine.acquire()
    +try:
    +    # play with the connection
    +finally:
    +    await conn.release()
    +
    +
    +

    where the connection should be manually returned to the pool with +conn.release().

    +

    Within the same context (usually the same Task, see +also 数据库事务), a nesting acquire by default re

    +
    +
    参数
    +
      +
    • timeout -- Block up to timeout seconds until there is one free +connection in the pool. Default is None - block forever until +succeeded. This has no effect when lazy=True, and depends on the +actual situation when reuse=True.

    • +
    • reuse -- Reuse the latest reusable acquired connection (before +it's returned to the pool) in current context if there is one, or +borrow a new one if none present. Default is False for always +borrow a new one. This is useful when you are in a nested method call +series, wishing to use the same connection without passing it around +as parameters. See also: 数据库事务. A reusing +connection is not reusable even if reusable=True. If the reused +connection happened to be a lazy one, then the reusing connection is +lazy too.

    • +
    • lazy -- Don't acquire the actual underlying connection yet - do it +only when needed. Default is False for always do it immediately. +This is useful before entering a code block which may or may not make +use of a given connection object. Feeding in a lazy connection will +save the borrow-return job if the connection is never used. If +setting reuse=True at the same time, then the reused connection - +if any - applies the same laziness. For example, reusing a lazy +connection with lazy=False will cause the reused connection to +acquire an underlying connection immediately.

    • +
    • reusable -- Mark this connection as reusable or otherwise. This +has no effect if it is a reusing connection. All reusable connections +are placed in a stack, any reusing acquire operation will always +reuse the top (latest) reusable connection. One reusable connection +may be reused by several reusing connections - they all share one +same underlying connection. Acquiring a connection with +reusable=False and reusing=False makes it a cleanly isolated +connection which is only referenced once here.

    • +
    +
    +
    返回
    +

    A GinoConnection object.

    +
    +
    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    Acquires a connection with reuse=True and runs +all() on it. reuse=True means you can safely +do this without borrowing more than one underlying connection:

    +
    async with engine.acquire():
    +    await engine.all('SELECT ...')
    +
    +
    +

    The same applies for other query methods.

    +
    + +
    +
    +async close()
    +

    Close the engine, by closing the underlying pool.

    +
    + +
    +
    +compile(clause, *multiparams, **params)
    +

    A shortcut for compile() on +the dialect, returns raw SQL string and parameters according to the +rules of the dialect.

    +
    + +
    +
    +connection_cls
    +

    Customizes the connection class to use, default is +GinoConnection.

    +

    GinoConnection 的别名

    +
    + +
    +
    +property current_connection
    +

    Gets the most recently acquired reusable connection in the context. +None if there is no such connection.

    +
    +
    返回
    +

    GinoConnection

    +
    +
    +
    + +
    +
    +property dialect
    +

    Read-only property for the +Dialect of this engine.

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs first(), See all().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    This requires that there is a reusable connection in the current +context, and an active transaction is present. Then its +GinoConnection.iterate() is executed and returned.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs one(), See all().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs one_or_none(), See all().

    +
    + +
    +
    +property raw_pool
    +

    Read-only access to the underlying database connection pool instance. +This depends on the actual dialect in use, Pool +of asyncpg for example.

    +
    + +
    +
    +repr(color=False)
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs scalar(), See all().

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs status(). See also all().

    +
    + +
    +
    +transaction(*args, timeout=None, reuse=True, reusable=True, **kwargs)
    +

    Borrows a new connection and starts a transaction with it.

    +

    Different to GinoConnection.transaction(), transaction on engine +level supports only managed usage:

    +
    async with engine.transaction() as tx:
    +    # play with transaction here
    +
    +
    +

    Where the implicitly acquired connection is available as +tx.connection.

    +

    By default, transaction() acquires connection with +reuse=True and reusable=True, that means it by default tries to +create a nested transaction instead of a new transaction on a new +connection. You can change the default behavior by setting these two +arguments.

    +

    The other arguments are the same as +transaction() on connection.

    + +
    +
    返回
    +

    A asynchronous context manager that yields a +GinoTransaction

    +
    +
    +
    + +
    +
    +update_execution_options(**opt)
    +

    Update the default execution_options dictionary +of this GinoEngine.

    + +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.exceptions.html b/docs/zh/1.1b2/reference/api/gino.exceptions.html new file mode 100644 index 0000000..a340c9b --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.exceptions.html @@ -0,0 +1,250 @@ + + + + + + + + gino.exceptions module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.exceptions module

    +
    +
    +exception gino.exceptions.GinoException
    +

    基类:Exception

    +
    + +
    +
    +exception gino.exceptions.InitializedError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.MultipleResultsFound
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoResultFound
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoSuchRowError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UninitializedError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UnknownJSONPropertyError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.ext.html b/docs/zh/1.1b2/reference/api/gino.ext.html new file mode 100644 index 0000000..966f211 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.ext.html @@ -0,0 +1,227 @@ + + + + + + + + gino.ext package - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.ext package

    +
    +

    Module contents

    +

    Namespace package for GINO extensions.

    +

    This namespace package didn't use any of the 3 official solutions. Instead, +gino.ext adds a hook into sys.meta_path, and utilize the Entry points +to find the extensions.

    +

    Any GINO extension package should provide an entry point like this:

    +
    [gino.extensions]
    +starlette = gino_starlette
    +
    +
    +

    So that the Python package gino_starlette will also be importable through +gino.ext.starlette.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.html b/docs/zh/1.1b2/reference/api/gino.html new file mode 100644 index 0000000..664945a --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.html @@ -0,0 +1,280 @@ + + + + + + + + gino package - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino package

    + + +
    +

    Module contents

    +
    +
    +gino.create_engine(*args, **kwargs)
    +

    Shortcut for sqlalchemy.create_engine() with strategy="gino".

    +
    +

    在 1.1 版更改: Added the bakery keyword argument, please see Bakery.

    +
    +
    +

    在 1.1 版更改: Added the prebake keyword argument to choose when to create the prepared +statements for the queries in the bakery:

    +
      +
    • Pre-bake immediately when connected to the database (default).

    • +
    • No pre-bake but create prepared statements lazily when needed for the first +time.

    • +
    +

    Note: prebake has no effect in aiomysql

    +
    +
    + +
    +
    +gino.get_version()
    +

    Get current GINO version.

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.json_support.html b/docs/zh/1.1b2/reference/api/gino.json_support.html new file mode 100644 index 0000000..903899c --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.json_support.html @@ -0,0 +1,350 @@ + + + + + + + + gino.json_support module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.json_support module

    +
    +
    +class gino.json_support.ArrayProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.BooleanProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.DateTimeProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.IntegerProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.JSONProperty(default=None, prop_name='profile')
    +

    基类:object

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +get_profile(instance)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    +
    +reload(instance)
    +
    + +
    +
    +save(instance, value=<object object>)
    +
    + +
    + +
    +
    +class gino.json_support.ObjectProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.StringProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.loader.html b/docs/zh/1.1b2/reference/api/gino.loader.html new file mode 100644 index 0000000..f43f27f --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.loader.html @@ -0,0 +1,633 @@ + + + + + + + + gino.loader module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.loader module

    +
    +
    +class gino.loader.AliasLoader(alias, *columns, **extras)
    +

    基类:gino.loader.ModelLoader

    +

    The same as ModelLoader, kept for compatibility.

    +
    + +
    +
    +class gino.loader.CallableLoader(func)
    +

    基类:gino.loader.Loader

    +

    Load the row by calling a specified function.

    +
    +
    参数
    +

    func -- A callable() taking 2 positional arguments (row, context) +that will be called in do_load(), returning the loaded result.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to the given function.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    The result calling the given function, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ColumnLoader(column)
    +

    基类:gino.loader.Loader

    +

    Load a given column in the row.

    +
    +
    参数
    +

    column -- The column name as str, or a +Column instance to avoid name conflict.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- Not used.

    • +
    +
    +
    返回
    +

    The value of the specified column, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.Loader
    +

    基类:object

    +

    The abstract base class of loaders.

    +

    Loaders are used to load raw database rows into expected results. +GinoEngine will use the loader set on the loader value of +the execution_options(), for example:

    +
    from sqlalchemy import text, create_engine
    +from gino.loader import ColumnLoader
    +
    +e = await create_engine("postgresql://localhost/gino", strategy="gino")
    +q = text("SELECT now() as ts")
    +loader = ColumnLoader("ts")
    +ts = await e.first(q.execution_options(loader=loader))  # datetime
    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    Must be implemented in subclasses.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    Any result that the loader is supposed to return, followed by a boolean +value indicating if the result is distinct.

    +
    +
    +
    + +
    +
    +classmethod get(value)
    +

    Automatically create a loader based on the type of the given value.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    value type

    loader type

    tuple

    TupleLoader

    callable()

    CallableLoader

    Column, +Label

    ColumnLoader

    Model

    ModelLoader

    Alias

    AliasLoader

    Loader

    as is

    any other types

    ValueLoader

    +
    +
    参数
    +

    value -- Any supported value above.

    +
    +
    返回
    +

    A loader instance.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +property query
    +

    Generate a query from this loader.

    +

    This is an experimental feature, not all loaders support this.

    +
    +
    返回
    +

    A query instance with the loader execution option set to self.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ModelLoader(model, *columns, **extras)
    +

    基类:gino.loader.Loader

    +

    A loader that loads a row into a GINO model instance.

    +

    This loader generates an instance of the given model type and fills the instance +with attributes according to the other given parameters:

    +
      +
    • Load each column attribute listed in the given columns positional arguments.

    • +
    • If columns is not given, all defined columns of the model will be loaded.

    • +
    • For each keyword argument, its value will be used to generate a loader using +Loader.get(), and the loaded value will be setattr() to the model +instance under the name of the key.

    • +
    +

    For example, the simplest select and load:

    +
    sqlalchemy.select([User]).execution_options(loader=ModelLoader(User))
    +
    +
    +

    Select only the name column, and still load it into model instances:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, User.name)
    +)
    +
    +
    +

    This would also yield User instances, with all column attributes as None but +name.

    +

    Nest a ValueLoader:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, id=1)
    +)
    +
    +
    +

    1 is then converted into a ValueLoader and mocked the id attribute +of all returned User instances as 1.

    +

    Nest another ModelLoader:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company)
    +)
    +
    +
    +

    Likewise, Company is converted into a ModelLoader to load the +Company columns from the joined result, and the Company instances are set to +the company attribute of each User instance using setattr().

    +
    +
    参数
    +
      +
    • model -- A subclass of Model to instantiate.

    • +
    • columns -- A list of Column or str to +load, default is all the columns in the model.

    • +
    • extras -- Additional attributes to load on the model instance.

    • +
    +
    +
    +
    +
    +distinct(*columns)
    +

    Configure this loader to reuse instances that have the same values of all the +give columns.

    +
    +
    参数
    +

    columns -- Preferably Column instances.

    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    The model instance, followed by a boolean value indicating if the +result is distinct.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +load(*columns, **extras)
    +

    Update the loader with new rules.

    +

    After initialization, the rules of this loader can still be updated. This is +useful when using the model class as a shortcut of ModelLoader where +possible, chaining with a load() to initialize the rules, for example:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company.load('name'))
    +)
    +
    +
    +
    +
    参数
    +
      +
    • columns -- If provided, replace the columns to load with the given ones.

    • +
    • extras -- Update the loader with new extras.

    • +
    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +none_as_none(enabled=True)
    +

    Deprecated method for compatibility, does nothing.

    +
    + +
    +
    +on(on_clause)
    +

    Specify the on_clause for generating joined queries.

    +

    This is an experimental feature, used by get_from().

    +
    +
    参数
    +

    on_clause -- An expression to feed into +join().

    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.TupleLoader(values)
    +

    基类:gino.loader.Loader

    +

    Load multiple values into a tuple.

    +
    +
    参数
    +

    values -- A tuple, each item is converted into a loader with +Loader.get().

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to sub-loaders.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    A tuple with loaded results from all sub-loaders, followed by +True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ValueLoader(value)
    +

    基类:gino.loader.Loader

    +

    A loader that always return the specified value.

    +
    +
    参数
    +

    value -- The value to return on load.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- Not used.

    • +
    • context -- Not used.

    • +
    +
    +
    返回
    +

    The given value, followed by True.

    +
    +
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.schema.html b/docs/zh/1.1b2/reference/api/gino.schema.html new file mode 100644 index 0000000..87c0eda --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.schema.html @@ -0,0 +1,328 @@ + + + + + + + + gino.schema module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.schema module

    +
    +
    +class gino.schema.AsyncSchemaDropper(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    基类:gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaDropper

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, drop_ok=False)
    +
    + +
    +
    +async visit_table(table, drop_ok=False, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaGenerator(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    基类:gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaGenerator

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, create_ok=False)
    +
    + +
    +
    +async visit_table(table, create_ok=False, include_foreign_key_constraints=None, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaTypeMixin
    +

    基类:object

    +
    +
    +async create_async(bind=None, checkfirst=False)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncVisitor
    +

    基类:object

    +
    +
    +async traverse_single(obj, **kw)
    +
    + +
    + +
    +
    +class gino.schema.GinoSchemaVisitor(item)
    +

    基类:object

    +
    +
    +async create(bind=None, *args, **kwargs)
    +
    + +
    +
    +async create_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    +
    +async drop(bind=None, *args, **kwargs)
    +
    + +
    +
    +async drop_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    + +
    +
    +gino.schema.patch_schema(db)
    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.strategies.html b/docs/zh/1.1b2/reference/api/gino.strategies.html new file mode 100644 index 0000000..4c47c24 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.strategies.html @@ -0,0 +1,236 @@ + + + + + + + + gino.strategies module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.strategies module

    +
    +
    +class gino.strategies.GinoStrategy
    +

    基类:sqlalchemy.engine.strategies.EngineStrategy

    +

    A SQLAlchemy engine strategy for GINO.

    +

    This strategy is initialized automatically as gino is imported.

    +

    If sqlalchemy.create_engine() uses strategy="gino", it will return a +Coroutine, and treat URL prefix postgresql:// or +postgres:// as postgresql+asyncpg://.

    +
    +
    +async create(name_or_url, loop=None, **kwargs)
    +

    Given arguments, returns a new Engine instance.

    +
    + +
    +
    +engine_cls
    +

    gino.engine.GinoEngine 的别名

    +
    + +
    +
    +name = 'gino'
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/api/gino.transaction.html b/docs/zh/1.1b2/reference/api/gino.transaction.html new file mode 100644 index 0000000..1171b10 --- /dev/null +++ b/docs/zh/1.1b2/reference/api/gino.transaction.html @@ -0,0 +1,332 @@ + + + + + + + + gino.transaction module - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.transaction module

    +
    +
    +class gino.transaction.GinoTransaction(conn, args, kwargs)
    +

    基类:object

    +

    Represents an underlying database transaction and its connection, offering +methods to manage this transaction.

    +

    GinoTransaction is supposed to be created by either +gino.engine.GinoConnection.transaction(), or +gino.engine.GinoEngine.transaction(), or +gino.api.Gino.transaction(), shown as follows:

    +
    async with db.transaction() as tx:
    +    ...
    +
    +async with engine.transaction() as tx:
    +    ...
    +
    +async with conn.transaction() as tx:
    +    ...
    +
    +tx = await conn.transaction()
    +try:
    +    ...
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    When in use with asynchronous context manager, GinoTransaction +will be in managed mode, while the last example with await will put +the GinoTransaction in manual mode where you have to call +the commit() or rollback() to manually close the transaction.

    +

    In managed mode the transaction will be automatically committed or +rolled back on exiting the async with block depending on whether there +is an exception or not. Meanwhile, you can explicitly exit the transaction +early by raise_commit() or raise_rollback() which will raise +an internal exception managed by the asynchronous context manager and +interpreted as a commit or rollback action. In a nested transaction +situation, the two exit-early methods always close up the very transaction +which the two methods are referenced upon - all children transactions are +either committed or rolled back correspondingly, while no parent +transaction was ever touched. For example:

    +
    async with db.transaction() as tx1:
    +    async with db.transaction() as tx2:
    +        async with db.transaction() as tx3:
    +            tx2.raise_rollback()
    +            # Won't reach here
    +        # Won't reach here
    +    # Continues here with tx1, with both tx2 and tx3 rolled back.
    +
    +    # For PostgreSQL, tx1 can still be committed successfully because
    +    # tx2 and tx3 are just SAVEPOINTs in transaction tx1
    +
    +
    +
    +

    小技巧

    +

    The internal exception raised from raise_commit() and +raise_rollback() is a subclass of BaseException, so +normal try ... except Exception: can't trap the commit or rollback.

    +
    +
    +
    +async commit()
    +

    Only available in manual mode: manually commit this transaction.

    +
    + +
    +
    +property connection
    +

    Accesses to the GinoConnection of this +transaction. This is useful if when the transaction is started from +db or engine where the connection is implicitly acquired for +you together with the transaction.

    +
    + +
    +
    +raise_commit()
    +

    Only available in managed mode: skip rest of the code in this +transaction and commit immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    async with db.transaction() as tx:
    +    await user.update(age=64).apply()
    +    tx.raise_commit()
    +    await user.update(age=32).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +raise_rollback()
    +

    Only available in managed mode: skip rest of the code in this +transaction and rollback immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    assert user.age == 64  # assumption
    +
    +async with db.transaction() as tx:
    +    await user.update(age=32).apply()
    +    tx.raise_rollback()
    +    await user.update(age=128).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +property raw_transaction
    +

    Accesses to the underlying transaction object, whose type depends on +the dialect in use.

    +
    + +
    +
    +async rollback()
    +

    Only available in manual mode: manually rollback this transaction.

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/extensions.html b/docs/zh/1.1b2/reference/extensions.html new file mode 100644 index 0000000..f8121cb --- /dev/null +++ b/docs/zh/1.1b2/reference/extensions.html @@ -0,0 +1,220 @@ + + + + + + + + 扩展 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/extensions/sanic.html b/docs/zh/1.1b2/reference/extensions/sanic.html new file mode 100644 index 0000000..3935dde --- /dev/null +++ b/docs/zh/1.1b2/reference/extensions/sanic.html @@ -0,0 +1,329 @@ + + + + + + + + Sanic Support - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/extensions/starlette.html b/docs/zh/1.1b2/reference/extensions/starlette.html new file mode 100644 index 0000000..e2ed0be --- /dev/null +++ b/docs/zh/1.1b2/reference/extensions/starlette.html @@ -0,0 +1,326 @@ + + + + + + + + Starlette Support - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    注解

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/extensions/tornado.html b/docs/zh/1.1b2/reference/extensions/tornado.html new file mode 100644 index 0000000..72493e8 --- /dev/null +++ b/docs/zh/1.1b2/reference/extensions/tornado.html @@ -0,0 +1,208 @@ + + + + + + + + Tornado Support - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/reference/history.html b/docs/zh/1.1b2/reference/history.html new file mode 100644 index 0000000..0062d14 --- /dev/null +++ b/docs/zh/1.1b2/reference/history.html @@ -0,0 +1,918 @@ + + + + + + + + 版本历史 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    版本历史

    +
    +

    GINO 1.1

    +
    +

    1.1.0 (pending)

    +
      +
    • Added baked query feature (#478 #659 #667)

    • +
    • Added Query.gino.execution_options shortcut (#659)

    • +
    • Added @db.declared_attr(with_table=True) (#659)

    • +
    +
    +
    +
    +

    GINO 1.0

    +
    +

    Migrating to GINO 1.0

    +

    GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +

    Extension Module

    Installation in GINO 1.0

    gino.ext.starlette

    pip install gino[starlette]

    gino.ext.aiohttp

    pip install gino[aiohttp]

    gino.ext.sanic

    pip install gino[sanic]

    gino.ext.tornado

    pip install gino[tornado]

    gino.ext.quart

    pip install gino[quart]

    +

    The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed gino[starlette]:

    +
    from gino.ext.starlette import Gino
    +
    +
    +

    GINO 1.0 switched to Poetry for package and +dependency management and started to use the +src layout. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process:

    +
      +
    • Source files are now located under src directory.

    • +
    • The src dist on PyPI does not include tests, docs and some other files due to +a limitation of Poetry.

    • +
    +
    +
    +

    1.0.1 (2020-06-08)

    +
      +
    • Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4)

    • +
    • Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672)

    • +
    • Multiple JSON property fixes (#661 #662 #695)

    • +
    • Fixed extension typing issue (#673 #674)

    • +
    • Fixed model override behavior (#694)

    • +
    • Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696)

    • +
    +
    +
    +

    1.0.0 (2020-04-26)

    +
      +
    • Switched to Poetry for package and dependency management.

    • +
    • [Breaking] Moved built-in extension modules to separate PyPI packages.

    • +
    • Switched to src layout.

    • +
    • Switched to black code style.

    • +
    • Better documentation.

    • +
    • [Breaking] none_as_none() is now always enabled.

    • +
    • Added representation method for engine.

    • +
    • Protected the URL instance fed to set_bind() from manipulation.

    • +
    • Replaced some assert with AssertionError (#258 #655)

    • +
    +
    +
    +
    +

    GINO 0.8

    +

    This is also version 1.0 release candidate.

    +
    +

    Migrating to GINO 0.8

    +
    +

    1. contextvars

    +

    We introduced aiocontextvars 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the enable_inherit() or +disable_inherit() calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created after importing gino or aiocontextvars, or the patch +won't work correctly.

    +

    There is nothing to worry about in Python 3.7.

    +
    +
    +

    2. none_as_none

    +

    When GINO tries to load a row with all NULL values into an instance, it +will now by default return None instead of an instance with all None +attributes. To recover the default behavior of 0.7, please specify +none_as_none(False) in affected model loader.

    +

    This is especially applicable to relationship sub-loaders - if the sub-loader +found it all NULL, no instance will be set to parent instance. For +example:

    +
    child = await Child.load(parent=Parent).query.gino.first()
    +
    +
    +

    If child.parent_id is NULL in database, then the child instance +won't be called with any setattr(child, 'parent', ...) at all. (If you need +child.parent == None in this case, consider setting default value +parent = None in child model.)

    +

    Please note, it is deprecated to disable none_as_none, and disabling will +be removed in GINO 1.0.

    +
    +
    +
    +

    0.8.7 (2020-04-19)

    +
      +
    • Improved error handling when attribute names collide (Contributed by Reskov in #637 #638)

    • +
    • Fixed with_bind usability in aiohttp extension (#518)

    • +
    +
    +
    +

    0.8.6 (2020-02-10)

    +
      +
    • Fixed JSONPathType bind processor for asyncpg (#609)

    • +
    • Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600)

    • +
    • Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629)

    • +
    • Added distinct() to Alias (#628)

    • +
    +
    +
    +

    0.8.5 (2019-11-19)

    +
      +
    • Improved support for __tablename__ in declared_attr (Contributed by Roald Storm in #592)

    • +
    +
    +
    +

    0.8.4 (2019-11-09)

    +
      +
    • Better loader support for models in subqueries (#573 #585)

    • +
    • Allowed __tablename__ to be a declared_attr (#579 #582)

    • +
    • Fixed Sanic 19.9.0 compatibility (#569)

    • +
    • Added one() and one_or_none() (Contributed by Ilaï Deutel in #577)

    • +
    • Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538)

    • +
    • Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533)

    • +
    • Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520)

    • +
    • Fixed Grammar (Contributed by Simeon J Morgan in #504)

    • +
    +
    +
    +

    0.8.3 (2019-06-06)

    +
      +
    • Fixed deprecated warnings in asyncpg dialect and aiohttp (#425)

    • +
    • Customizable db attribute name in aiohttp app instance (#457)

    • +
    • Added Starlette support (#486)

    • +
    +
    +
    +

    0.8.2 (2019-03-07)

    +
      +
    • Added exception for unknown JSON properties (#406 #408)

    • +
    • Supported Quart 0.7 (#411)

    • +
    • Accepted kwargs for db init in extensions (#407 #427)

    • +
    • Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440)

    • +
    • Added NullPool (#437 #441)

    • +
    • Unpinned dependency versions (#447)

    • +
    • Added support for SQLAlchemy 1.3 (#433 #451)

    • +
    +
    +
    +

    0.8.1 (2018-12-08)

    +
      +
    • Alias supported Label (#365)

    • +
    • Docs update (#308, 4c59ad, #401 by Pascal van Kooten)

    • +
    • Version requirement for SQLAlchemy is updated to >=1.2 (#378 #382)

    • +
    • Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) +* And all other extensions (#395)

    • +
    • Supported Tornado 5 (#396, also thanks to Vladimir Goncharov)

    • +
    • Fixed custom JSON/JSONB type support (#402 #403)

    • +
    +

    (Most fixes done by Tony Wang)

    +
    +
    +

    0.8.0 (2018-10-16)

    +
      +
    • Welcome Tony Wang to the maintenance team (#335)

    • +
    • Allowed custom column names (#261 #297)

    • +
    • Allowed column instance in model.load() (Contributed by Jekel in #323)

    • +
    • [Breaking] Upgraded to aiocontextvars 0.2.0 (#333)

    • +
    • Fixed bug that the same empty stack is shared between sub-tasks (#313 #334)

    • +
    • [Breaking] Made none_as_none() the default behavior (#351)

    • +
    • Bug fixes and docs update

    • +
    +
    +
    +
    +

    GINO 0.7

    +

    This is also version 1.0 beta 3.

    +
    +

    0.7.7 (2018-12-08)

    +
      +
    • Backported fix for custom JSON/JSONB type support (#402 #403)

    • +
    +
    +
    +

    0.7.6 (2018-08-26)

    +
      +
    • Updated library support (Contributed by Tony Wang in #275 #309)

    • +
    • Added none_as_none() (#281 #282)

    • +
    • Added ARRAY alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289)

    • +
    • Added Model.lookup() to prevent updating whole table without primary key (#287 #288)

    • +
    • Added DB_ECHO in extension options (Contributed by Mykyta Holubakha in #298)

    • +
    • Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305)

    • +
    • Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304)

    • +
    • Fixed to raise UninitializedError if bind is None (Contributed by Tony Wang in #307 #310)

    • +
    +
    +
    +

    0.7.5 (2018-07-26)

    +
      +
    • Added friendly error message when using abstract models by mistake (#224)

    • +
    • Supported Python 3.7 (Contributed by Tony Wang in #265)

    • +
    • Updated documentation

    • +
    • Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280)

    • +
    +
    +
    +

    0.7.4 (2018-06-10)

    +
      +
    • Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228)

    • +
    • Added Quart support (#213)

    • +
    • Fixed Tornado options parsing (#231)

    • +
    • Improved coding style and test coverage

    • +
    +
    +
    +

    0.7.3 (2018-05-19)

    +
      +
    • Fix for failing binary type (#225)

    • +
    +
    +
    +

    0.7.2 (2018-05-15)

    +
      +
    • Added prepared statement support (#14)

    • +
    • Added dsn in extension config (Contributed by Yurii Shtrikker in #215)

    • +
    +
    +
    +

    0.7.1 (2018-05-03)

    +
      +
    • Added support for inline model constraints (Contributed by Kinware in #198)

    • +
    • Added docs and tests for using SSL (#202)

    • +
    • Added declared_attr (#204)

    • +
    • Allowed ModelLoader passively load partial model (#216)

    • +
    +
    +
    +

    0.7.0 (2018-04-18)

    +
      +
    • Added Python 3.5 support (#187)

    • +
    • Added support to use dict as ident for Model.get (#192)

    • +
    • Added result loader (partial relationship support) (#13)

    • +
    • Added documentation on relationship and transaction (#146)

    • +
    +
    +
    +
    +

    GINO 0.6

    +

    This is also version 1.0 beta 2.

    +
    +

    Migrating to GINO 0.6

    +
    +

    1. Task Local

    +

    We created a new Python package aiocontextvars from previous local.py. If +you made use of the task local features, you should install this package.

    +

    Previous gino.enable_task_local() and gino.disable_task_local() are +replaced by aiocontextvars.enable_inherit() and +aiocontextvars.disable_inherit(). However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars by default offers task +local even without enable_inherit(), which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars is installed.

    +

    There is no gino.get_local() and gino.reset_local() relevant in +aiocontextvars. The similar thing is aiocontextvars.ContextVar instance +through its get(), set() and delete() methods.

    +

    Previous gino.is_local_root() is now +not aiocontextvars.Context.current().inherited.

    +
    +
    +

    2. Engine

    +

    GINO 0.6 hides asyncpg.Pool behind the new SQLAlchemy-alike +gino.GinoEngine. Instead of doing this in 0.5:

    +
    async with db.create_pool('postgresql://...') as pool:
    +    # your code here
    +
    +
    +

    You should change it to this in 0.6:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +

    This equals to:

    +
    engine = await gino.create_engine('postgresql://...')
    +db.bind = engine
    +try:
    +    # your code here
    +finally:
    +    db.bind = None
    +    await engine.close()
    +
    +
    +

    Or:

    +
    engine = await db.set_bind('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Or even this:

    +
    db = await gino.Gino('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Choose whichever suits you the best.

    +

    Obviously GinoEngine doesn't provide asyncpg.Pool methods directly any +longer, but you can get the underlying asyncpg.Pool object through +engine.raw_pool property.

    +

    GinoPool.get_current_connection() is now changed to current_connection +property on GinoEngine instances to support multiple engines.

    +

    GinoPool.execution_option is gone, instead update_execution_options() +on GinoEngine instance is available.

    +

    GinoPool().metadata is gone, dialect is still available.

    +

    GinoPool.release() is removed in GinoEngine and Gino, the +release() method on GinoConnection object should be used instead.

    +

    These methods exist both in 0.5 GinoPool and 0.6 GinoEngine: +close(), acquire(), all(), first(), scalar(), status().

    +
    +
    +

    3. GinoConnection

    +

    Similarly, GinoConnection in 0.6 is no longer a subclass of +asyncpg.Connection, instead it has a asyncpg.Connection instance, +accessable through GinoConnection.raw_connection property.

    +

    GinoConnection.metadata is deleted in 0.6, while dialect remained.

    +

    GinoConnection.execution_options() is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior.

    +

    GinoConnection.release() is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +permanent=False to remain its previous behavior.

    +

    And all(), first(), scalar(), status(), iterate(), +transaction() remained in 0.6.

    +
    +
    +

    4. Query API

    +

    All five query APIs all(), first(), scalar(), status(), +iterate() now accept the same parameters as SQLAlchemy execute(), +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter bind if they did. Just use the API on the GinoEngine or +GinoConnection object instead.

    +
    +
    +

    5. Transaction

    +

    Transaction interface is rewritten. Now in 0.6, a GinoTransaction object is +provided consistently from all 3 methods:

    +
    async with db.transaction() as tx:
    +    # within transaction
    +
    +async with engine.transaction() as tx:
    +    # within transaction
    +
    +async with engine.acquire() as conn:
    +    async with conn.transaction() as tx:
    +        # within transaction
    +
    +
    +

    And different usage with await:

    +
    tx = await db.transaction()
    +try:
    +    # within transaction
    +    await tx.commit()
    +except:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    The GinoConnection object is available at tx.connection, while +underlying transaction object from database driver is available at +tx.transaction - for asyncpg it is an asyncpg.transaction.Transaction +object.

    +
    +
    +
    +

    0.6.6 (2018-05-18)

    +
      +
    • Backported a fix for failing binary type (#225)

    • +
    +
    +
    +

    0.6.5 (2018-04-18)

    +
      +
    • Abandoned 0.6.4 and keep 0.6.x stable

    • +
    • Backported doc for transaction

    • +
    +
    +
    +

    0.6.4 (2018-04-16)

    +

    Abandoned version, please use 0.7.0 instead.

    +
    +
    +

    0.6.3 (2018-04-08)

    +
      +
    • Added aiohttp support

    • +
    • Added support for calling create() on model instances (Contributed by Kinware in #178 #180)

    • +
    • Fixed get() by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184)

    • +
    +
    +
    +

    0.6.2 (2018-03-24)

    +
      +
    • Fixed SQLAlchemy prefetch issue (#141)

    • +
    • Fixed issue that mixin class on Model not working (#174)

    • +
    • Added more documentation (Thanks Olaf Conradi for reviewing)

    • +
    +
    +
    +

    0.6.1 (2018-03-18)

    +
      +
    • Fixed create and drop for Enum type (#160)

    • +
    • A bit more documentation (#159)

    • +
    +
    +
    +

    0.6.0 (2018-03-14)

    +
      +
    • [Breaking] API Refactored, Pool replaced with Engine

      +
        +
      • New API Engine replaced asyncpg Pool (#59)

      • +
      • Supported different dialects, theoretically

      • +
      • Used aiocontextvars instead of builtin task local (#89)

      • +
      +
    • +
    • [Breaking] Fixed query API with multiparams (executemany) to return correctly (#20)

    • +
    • [Breaking] The query methods no longer accept the parameter bind

    • +
    • [Breaking] Gino no longer exposes postgresql types

    • +
    • Added echo on engine (#142)

    • +
    • Added tests to cover 80% of code

    • +
    • Added gino extension on SchemaItem for create_all and so on (#76 #106)

    • +
    • Added gino extension on model classes for create() or drop()

    • +
    • Added _update_request_cls on CRUDModel (#147)

    • +
    • Rewrote the documentation (#146)

    • +
    +
    +
    +
    +

    GINO 0.5

    +

    This is also version 1.0 beta 1.

    +
    +

    0.5.8 (2018-02-14)

    +
      +
    • Preparing for 0.6.0 which will be a breaking release

    • +
    • Fixed wrong value of Enum in creation (Contributed by Sergey Kovalev in #126)

    • +
    +
    +
    +

    0.5.7 (2017-11-24)

    +

    This is an emergency fix for 0.5.6.

    +
      +
    • Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114)

    • +
    • Added Model.outerjoin

    • +
    +
    +
    +

    0.5.6 (2017-11-23)

    +
      +
    • Changed to use unnamed statement when possible (#80 #90)

    • +
    • Added more example (Contributed by Kentoseth in #109)

    • +
    • Added Model.join and made Model selectable (Contributed by Ádám Barancsuk in #112 #113)

    • +
    +
    +
    +

    0.5.5 (2017-10-18)

    +
      +
    • Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87)

    • +
    • Added ability to reset local storage (#84)

    • +
    • Fixed bug in JSON property update

    • +
    • Added update chaining feature

    • +
    +
    +
    +

    0.5.4 (2017-10-04)

    +
      +
    • Updated example (Contributed by Kinware in #75)

    • +
    • Added Model.insert (Contributed by Neal Wang in #63)

    • +
    • Fixed issue that non-lazy acquiring fails dirty (#79)

    • +
    +
    +
    +

    0.5.3 (2017-09-23)

    +
      +
    • Fixed no module named cutils error (Contributed by Vladimir Goncharov in #73)

    • +
    +
    +
    +

    0.5.2 (2017-09-10)

    +
      +
    • Added missing driver name on dialect (#67)

    • +
    • Fixed dialect to support native decimal type (#67)

    • +
    +
    +
    +

    0.5.1 (2017-09-09)

    +

    This is an emergency fix for 0.5.0.

    +
      +
    • Reverted the extension, back to pure Python (#60)

    • +
    • Used SQLAlchemy RowProxy

    • +
    • Added first_or_404

    • +
    • Fixed bug that GinoPool cannot be inherited

    • +
    +
    +
    +

    0.5.0 (2017-09-03)

    +
      +
    • [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten

      +
        +
      • Extracted CRUD operations

      • +
      • Core operations are moved to dialect and execution context

      • +
      • Removed guess_model, switched to explicit execution options

      • +
      • Turned timeout parameter to an execution option

      • +
      • Extracted pool, connection and api from asyncpg_delegate

      • +
      +
    • +
    • Added support for SQLAlchemy execution options, and a few custom options

    • +
    • [Breaking] Made Model.select return rows by default (#39)

    • +
    • Moved get_or_404 to extensions (#38)

    • +
    • Added iterator on model classes (#43)

    • +
    • Added Tornado extension (Contributed by Vladimir Goncharov)

    • +
    • Added Model.to_dict (#47)

    • +
    • Added an extension module to update asyncpg.Record with processed results

    • +
    +
    +
    +
    +

    Early Development Releases

    +

    Considered as alpha releases.

    +
    +

    0.4.1 (2017-08-20)

    +
      +
    • Support select on model instance

    • +
    +
    +
    +

    0.4.0 (2017-08-15)

    +
      +
    • Made get_or_404 more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31)

    • +
    • Delegated sqlalchemy.__all__ (Contributed by Neal Wang in #10 #33)

    • +
    • [Breaking] Rewrote JSON/JSONB support (#29)

    • +
    • Added lazy parameter on db.acquire (Contributed by Binghan Li in #32)

    • +
    • Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34)

    • +
    • Fixed iterate API to be compatible with asyncpg (#32)

    • +
    • Unified exceptions

    • +
    • [Breaking] Changed update API (#29)

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.3.0 (2017-08-07)

    +
      +
    • Supported __table_args__ (#12)

    • +
    • Introduced task local to manage connection in context (#19)

    • +
    • Added query.gino extension for in-place execution

    • +
    • Refreshed README (#3)

    • +
    • Adopted PEP 487 (Contributed by Tony Wang in #17 #27)

    • +
    • Used weakref on __model__ of table and query (Contributed by Tony Wang)

    • +
    • Delegated asyncpg timeout parameter (Contributed by Neal Wang in #16 #22)

    • +
    +
    +
    +

    0.2.3 (2017-08-04)

    +
      +
    • Supported any primary key (Contributed by Tony Wang in #11)

    • +
    +
    +
    +

    0.2.2 (2017-08-02)

    +
      +
    • Supported SQLAlchemy result processor

    • +
    • Added rich support on JSON/JSONB

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.2.1 (2017-07-28)

    +
      +
    • Added update and delete API

    • +
    +
    +
    +

    0.2.0 (2017-07-28)

    +
      +
    • Changed API, no longer reuses asyncpg API

    • +
    +
    +
    +

    0.1.1 (2017-07-25)

    +
      +
    • Added db.bind

    • +
    • API changed: parameter conn renamed to optional bind

    • +
    • Delegated asyncpg Pool with db.create_pool

    • +
    • Internal enhancement and bug fixes

    • +
    +
    +
    +

    0.1.0 (2017-07-21)

    +
      +
    • First release on PyPI.

    • +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/search.html b/docs/zh/1.1b2/search.html new file mode 100644 index 0000000..2500d6c --- /dev/null +++ b/docs/zh/1.1b2/search.html @@ -0,0 +1 @@ +

    search.html

    \ No newline at end of file diff --git a/docs/zh/1.1b2/searchindex.js b/docs/zh/1.1b2/searchindex.js new file mode 100644 index 0000000..50195bb --- /dev/null +++ b/docs/zh/1.1b2/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["explanation","explanation/async","explanation/engine","explanation/sa20","explanation/why","how-to","how-to/alembic","how-to/bakery","how-to/contributing","how-to/crud","how-to/faq","how-to/json-props","how-to/loaders","how-to/pool","how-to/schema","how-to/transaction","index","reference","reference/api","reference/api/gino","reference/api/gino.aiocontextvars","reference/api/gino.api","reference/api/gino.bakery","reference/api/gino.crud","reference/api/gino.declarative","reference/api/gino.dialects","reference/api/gino.dialects.aiomysql","reference/api/gino.dialects.asyncpg","reference/api/gino.dialects.base","reference/api/gino.engine","reference/api/gino.exceptions","reference/api/gino.ext","reference/api/gino.json_support","reference/api/gino.loader","reference/api/gino.schema","reference/api/gino.strategies","reference/api/gino.transaction","reference/extensions","reference/extensions/sanic","reference/extensions/starlette","reference/extensions/tornado","reference/history","tutorials","tutorials/announcement","tutorials/fastapi","tutorials/tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["explanation.rst","explanation/async.rst","explanation/engine.rst","explanation/sa20.rst","explanation/why.rst","how-to.rst","how-to/alembic.rst","how-to/bakery.rst","how-to/contributing.rst","how-to/crud.rst","how-to/faq.rst","how-to/json-props.rst","how-to/loaders.rst","how-to/pool.rst","how-to/schema.rst","how-to/transaction.rst","index.rst","reference.rst","reference/api.rst","reference/api/gino.rst","reference/api/gino.aiocontextvars.rst","reference/api/gino.api.rst","reference/api/gino.bakery.rst","reference/api/gino.crud.rst","reference/api/gino.declarative.rst","reference/api/gino.dialects.rst","reference/api/gino.dialects.aiomysql.rst","reference/api/gino.dialects.asyncpg.rst","reference/api/gino.dialects.base.rst","reference/api/gino.engine.rst","reference/api/gino.exceptions.rst","reference/api/gino.ext.rst","reference/api/gino.json_support.rst","reference/api/gino.loader.rst","reference/api/gino.schema.rst","reference/api/gino.strategies.rst","reference/api/gino.transaction.rst","reference/extensions.rst","reference/extensions/sanic.rst","reference/extensions/starlette.rst","reference/extensions/tornado.rst","reference/history.rst","tutorials.rst","tutorials/announcement.rst","tutorials/fastapi.rst","tutorials/tutorial.rst"],objects:{"":{gino:[19,0,0,"-"]},"gino.aiocontextvars":{patch_asyncio:[20,1,1,""]},"gino.api":{Gino:[21,2,1,""],GinoExecutor:[21,2,1,""]},"gino.api.Gino":{Model:[21,3,1,""],acquire:[21,3,1,""],all:[21,3,1,""],bake:[21,3,1,""],bakery:[21,3,1,""],bind:[21,3,1,""],compile:[21,3,1,""],first:[21,3,1,""],iterate:[21,3,1,""],model_base_classes:[21,4,1,""],no_delegate:[21,4,1,""],one:[21,3,1,""],one_or_none:[21,3,1,""],pop_bind:[21,3,1,""],query_executor:[21,4,1,""],scalar:[21,3,1,""],schema_visitor:[21,4,1,""],set_bind:[21,3,1,""],status:[21,3,1,""],transaction:[21,3,1,""],with_bind:[21,3,1,""]},"gino.api.GinoExecutor":{all:[21,3,1,""],execution_options:[21,3,1,""],first:[21,3,1,""],iterate:[21,3,1,""],load:[21,3,1,""],model:[21,3,1,""],one:[21,3,1,""],one_or_none:[21,3,1,""],query:[21,3,1,""],return_model:[21,3,1,""],scalar:[21,3,1,""],status:[21,3,1,""],timeout:[21,3,1,""]},"gino.bakery":{BakedQuery:[22,2,1,""],Bakery:[22,2,1,""]},"gino.bakery.BakedQuery":{bind:[22,3,1,""],compiled_sql:[22,3,1,""],execution_options:[22,3,1,""],get:[22,3,1,""],query:[22,3,1,""],sql:[22,3,1,""]},"gino.bakery.Bakery":{bake:[22,3,1,""],query_cls:[22,4,1,""]},"gino.crud":{Alias:[23,2,1,""],CRUDModel:[23,2,1,""],QueryModel:[23,2,1,""],UpdateRequest:[23,2,1,""]},"gino.crud.Alias":{distinct:[23,3,1,""],load:[23,3,1,""],on:[23,3,1,""]},"gino.crud.CRUDModel":{"delete":[23,4,1,""],alias:[23,3,1,""],append_where_primary_key:[23,3,1,""],create:[23,3,1,""],distinct:[23,3,1,""],get:[23,3,1,""],in_query:[23,3,1,""],load:[23,3,1,""],lookup:[23,3,1,""],none_as_none:[23,3,1,""],on:[23,3,1,""],query:[23,4,1,""],select:[23,3,1,""],to_dict:[23,3,1,""],update:[23,4,1,""]},"gino.crud.UpdateRequest":{apply:[23,3,1,""],update:[23,3,1,""]},"gino.declarative":{ColumnAttribute:[24,2,1,""],InvertDict:[24,2,1,""],Model:[24,2,1,""],declarative_base:[24,1,1,""],declared_attr:[24,1,1,""]},"gino.declarative.InvertDict":{invert_get:[24,3,1,""]},"gino.dialects":{aiomysql:[26,0,0,"-"],asyncpg:[27,0,0,"-"],base:[28,0,0,"-"]},"gino.dialects.aiomysql":{AiomysqlDBAPI:[26,2,1,""],AiomysqlDialect:[26,2,1,""],AiomysqlExecutionContext:[26,2,1,""],AiomysqlIterator:[26,2,1,""],AsyncEnum:[26,2,1,""],DBAPICursor:[26,2,1,""],GinoNullType:[26,2,1,""],Pool:[26,2,1,""],Transaction:[26,2,1,""]},"gino.dialects.aiomysql.AiomysqlDBAPI":{paramstyle:[26,4,1,""]},"gino.dialects.aiomysql.AiomysqlDialect":{colspecs:[26,4,1,""],cursor_cls:[26,4,1,""],dbapi_class:[26,4,1,""],driver:[26,4,1,""],execution_ctx_cls:[26,4,1,""],get_isolation_level:[26,3,1,""],has_table:[26,3,1,""],init_kwargs:[26,4,1,""],init_pool:[26,3,1,""],on_connect:[26,3,1,""],postfetch_lastrowid:[26,4,1,""],set_isolation_level:[26,3,1,""],statement_compiler:[26,4,1,""],support_prepare:[26,4,1,""],support_returning:[26,4,1,""],supports_native_decimal:[26,4,1,""],transaction:[26,3,1,""]},"gino.dialects.aiomysql.AiomysqlExecutionContext":{get_affected_rows:[26,3,1,""],get_lastrowid:[26,3,1,""]},"gino.dialects.aiomysql.AiomysqlIterator":{forward:[26,3,1,""],many:[26,3,1,""],next:[26,3,1,""]},"gino.dialects.aiomysql.AsyncEnum":{create_async:[26,3,1,""],drop_async:[26,3,1,""]},"gino.dialects.aiomysql.DBAPICursor":{async_execute:[26,3,1,""],description:[26,3,1,""],execute_baked:[26,3,1,""],get_statusmsg:[26,3,1,""],iterate:[26,3,1,""],prepare:[26,3,1,""]},"gino.dialects.aiomysql.GinoNullType":{result_processor:[26,3,1,""]},"gino.dialects.aiomysql.Pool":{acquire:[26,3,1,""],close:[26,3,1,""],raw_pool:[26,3,1,""],release:[26,3,1,""],repr:[26,3,1,""]},"gino.dialects.aiomysql.Transaction":{begin:[26,3,1,""],commit:[26,3,1,""],raw_transaction:[26,3,1,""],rollback:[26,3,1,""]},"gino.dialects.asyncpg":{AsyncEnum:[27,2,1,""],AsyncpgCompiler:[27,2,1,""],AsyncpgCursor:[27,2,1,""],AsyncpgDBAPI:[27,2,1,""],AsyncpgDialect:[27,2,1,""],AsyncpgExecutionContext:[27,2,1,""],AsyncpgIterator:[27,2,1,""],AsyncpgJSONPathType:[27,2,1,""],DBAPICursor:[27,2,1,""],GinoNullType:[27,2,1,""],NullPool:[27,2,1,""],Pool:[27,2,1,""],PreparedStatement:[27,2,1,""],Transaction:[27,2,1,""]},"gino.dialects.asyncpg.AsyncEnum":{create_async:[27,3,1,""],drop_async:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgCompiler":{bindtemplate:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgCursor":{forward:[27,3,1,""],many:[27,3,1,""],next:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgDBAPI":{Error:[27,4,1,""]},"gino.dialects.asyncpg.AsyncpgDialect":{colspecs:[27,4,1,""],cursor_cls:[27,4,1,""],dbapi_class:[27,4,1,""],driver:[27,4,1,""],execution_ctx_cls:[27,4,1,""],get_isolation_level:[27,3,1,""],has_schema:[27,3,1,""],has_sequence:[27,3,1,""],has_table:[27,3,1,""],has_type:[27,3,1,""],init_kwargs:[27,4,1,""],init_pool:[27,3,1,""],on_connect:[27,3,1,""],set_isolation_level:[27,3,1,""],statement_compiler:[27,4,1,""],supports_native_decimal:[27,4,1,""],transaction:[27,3,1,""]},"gino.dialects.asyncpg.AsyncpgJSONPathType":{bind_processor:[27,3,1,""]},"gino.dialects.asyncpg.DBAPICursor":{async_execute:[27,3,1,""],description:[27,3,1,""],execute_baked:[27,3,1,""],get_statusmsg:[27,3,1,""],prepare:[27,3,1,""]},"gino.dialects.asyncpg.GinoNullType":{result_processor:[27,3,1,""]},"gino.dialects.asyncpg.NullPool":{acquire:[27,3,1,""],close:[27,3,1,""],raw_pool:[27,3,1,""],release:[27,3,1,""],repr:[27,3,1,""]},"gino.dialects.asyncpg.Pool":{acquire:[27,3,1,""],close:[27,3,1,""],raw_pool:[27,3,1,""],release:[27,3,1,""],repr:[27,3,1,""]},"gino.dialects.asyncpg.Transaction":{begin:[27,3,1,""],commit:[27,3,1,""],raw_transaction:[27,3,1,""],rollback:[27,3,1,""]},"gino.dialects.base":{AsyncDialectMixin:[28,2,1,""],BaseDBAPI:[28,2,1,""],Cursor:[28,2,1,""],DBAPICursor:[28,2,1,""],ExecutionContextOverride:[28,2,1,""],Pool:[28,2,1,""],PreparedStatement:[28,2,1,""],Transaction:[28,2,1,""]},"gino.dialects.base.AsyncDialectMixin":{compile:[28,3,1,""],cursor_cls:[28,4,1,""],dbapi:[28,3,1,""],dbapi_class:[28,4,1,""],init_pool:[28,3,1,""],support_prepare:[28,4,1,""],support_returning:[28,4,1,""],transaction:[28,3,1,""]},"gino.dialects.base.BaseDBAPI":{Binary:[28,3,1,""],Error:[28,4,1,""],paramstyle:[28,4,1,""]},"gino.dialects.base.Cursor":{forward:[28,3,1,""],many:[28,3,1,""],next:[28,3,1,""]},"gino.dialects.base.DBAPICursor":{async_execute:[28,3,1,""],description:[28,3,1,""],execute:[28,3,1,""],execute_baked:[28,3,1,""],executemany:[28,3,1,""],get_statusmsg:[28,3,1,""],prepare:[28,3,1,""]},"gino.dialects.base.ExecutionContextOverride":{baked_query:[28,4,1,""],get_affected_rows:[28,3,1,""],get_lastrowid:[28,3,1,""],get_result_proxy:[28,3,1,""],loader:[28,4,1,""],model:[28,4,1,""],process_rows:[28,3,1,""],return_model:[28,4,1,""],timeout:[28,4,1,""]},"gino.dialects.base.Pool":{acquire:[28,3,1,""],close:[28,3,1,""],raw_pool:[28,3,1,""],release:[28,3,1,""],repr:[28,3,1,""]},"gino.dialects.base.PreparedStatement":{all:[28,3,1,""],first:[28,3,1,""],iterate:[28,3,1,""],scalar:[28,3,1,""],status:[28,3,1,""]},"gino.dialects.base.Transaction":{begin:[28,3,1,""],commit:[28,3,1,""],raw_transaction:[28,3,1,""],rollback:[28,3,1,""]},"gino.engine":{GinoConnection:[29,2,1,""],GinoEngine:[29,2,1,""]},"gino.engine.GinoConnection":{all:[29,3,1,""],dialect:[29,3,1,""],execution_options:[29,3,1,""],first:[29,3,1,""],get_raw_connection:[29,3,1,""],iterate:[29,3,1,""],one:[29,3,1,""],one_or_none:[29,3,1,""],prepare:[29,3,1,""],raw_connection:[29,3,1,""],release:[29,3,1,""],scalar:[29,3,1,""],schema_for_object:[29,4,1,""],status:[29,3,1,""],transaction:[29,3,1,""]},"gino.engine.GinoEngine":{acquire:[29,3,1,""],all:[29,3,1,""],close:[29,3,1,""],compile:[29,3,1,""],connection_cls:[29,4,1,""],current_connection:[29,3,1,""],dialect:[29,3,1,""],first:[29,3,1,""],iterate:[29,3,1,""],one:[29,3,1,""],one_or_none:[29,3,1,""],raw_pool:[29,3,1,""],repr:[29,3,1,""],scalar:[29,3,1,""],status:[29,3,1,""],transaction:[29,3,1,""],update_execution_options:[29,3,1,""]},"gino.exceptions":{GinoException:[30,5,1,""],InitializedError:[30,5,1,""],MultipleResultsFound:[30,5,1,""],NoResultFound:[30,5,1,""],NoSuchRowError:[30,5,1,""],UninitializedError:[30,5,1,""],UnknownJSONPropertyError:[30,5,1,""]},"gino.json_support":{ArrayProperty:[32,2,1,""],BooleanProperty:[32,2,1,""],DateTimeProperty:[32,2,1,""],IntegerProperty:[32,2,1,""],JSONProperty:[32,2,1,""],ObjectProperty:[32,2,1,""],StringProperty:[32,2,1,""]},"gino.json_support.ArrayProperty":{decode:[32,3,1,""],encode:[32,3,1,""]},"gino.json_support.BooleanProperty":{decode:[32,3,1,""],encode:[32,3,1,""],make_expression:[32,3,1,""]},"gino.json_support.DateTimeProperty":{decode:[32,3,1,""],encode:[32,3,1,""],make_expression:[32,3,1,""]},"gino.json_support.IntegerProperty":{decode:[32,3,1,""],encode:[32,3,1,""],make_expression:[32,3,1,""]},"gino.json_support.JSONProperty":{decode:[32,3,1,""],encode:[32,3,1,""],get_profile:[32,3,1,""],make_expression:[32,3,1,""],reload:[32,3,1,""],save:[32,3,1,""]},"gino.json_support.ObjectProperty":{decode:[32,3,1,""],encode:[32,3,1,""]},"gino.json_support.StringProperty":{make_expression:[32,3,1,""]},"gino.loader":{AliasLoader:[33,2,1,""],CallableLoader:[33,2,1,""],ColumnLoader:[33,2,1,""],Loader:[33,2,1,""],ModelLoader:[33,2,1,""],TupleLoader:[33,2,1,""],ValueLoader:[33,2,1,""]},"gino.loader.CallableLoader":{do_load:[33,3,1,""]},"gino.loader.ColumnLoader":{do_load:[33,3,1,""]},"gino.loader.Loader":{do_load:[33,3,1,""],get:[33,3,1,""],get_columns:[33,3,1,""],get_from:[33,3,1,""],query:[33,3,1,""]},"gino.loader.ModelLoader":{distinct:[33,3,1,""],do_load:[33,3,1,""],get_columns:[33,3,1,""],get_from:[33,3,1,""],load:[33,3,1,""],none_as_none:[33,3,1,""],on:[33,3,1,""]},"gino.loader.TupleLoader":{do_load:[33,3,1,""]},"gino.loader.ValueLoader":{do_load:[33,3,1,""]},"gino.schema":{AsyncSchemaDropper:[34,2,1,""],AsyncSchemaGenerator:[34,2,1,""],AsyncSchemaTypeMixin:[34,2,1,""],AsyncVisitor:[34,2,1,""],GinoSchemaVisitor:[34,2,1,""],patch_schema:[34,1,1,""]},"gino.schema.AsyncSchemaDropper":{visit_foreign_key_constraint:[34,3,1,""],visit_index:[34,3,1,""],visit_metadata:[34,3,1,""],visit_sequence:[34,3,1,""],visit_table:[34,3,1,""]},"gino.schema.AsyncSchemaGenerator":{visit_foreign_key_constraint:[34,3,1,""],visit_index:[34,3,1,""],visit_metadata:[34,3,1,""],visit_sequence:[34,3,1,""],visit_table:[34,3,1,""]},"gino.schema.AsyncSchemaTypeMixin":{create_async:[34,3,1,""],drop_async:[34,3,1,""]},"gino.schema.AsyncVisitor":{traverse_single:[34,3,1,""]},"gino.schema.GinoSchemaVisitor":{create:[34,3,1,""],create_all:[34,3,1,""],drop:[34,3,1,""],drop_all:[34,3,1,""]},"gino.strategies":{GinoStrategy:[35,2,1,""]},"gino.strategies.GinoStrategy":{create:[35,3,1,""],engine_cls:[35,4,1,""],name:[35,4,1,""]},"gino.transaction":{GinoTransaction:[36,2,1,""]},"gino.transaction.GinoTransaction":{commit:[36,3,1,""],connection:[36,3,1,""],raise_commit:[36,3,1,""],raise_rollback:[36,3,1,""],raw_transaction:[36,3,1,""],rollback:[36,3,1,""]},gino:{aiocontextvars:[20,0,0,"-"],api:[21,0,0,"-"],bakery:[22,0,0,"-"],create_engine:[19,1,1,""],crud:[23,0,0,"-"],declarative:[24,0,0,"-"],dialects:[25,0,0,"-"],engine:[29,0,0,"-"],exceptions:[30,0,0,"-"],ext:[31,0,0,"-"],get_version:[19,1,1,""],json_support:[32,0,0,"-"],loader:[33,0,0,"-"],schema:[34,0,0,"-"],strategies:[35,0,0,"-"],transaction:[36,0,0,"-"]}},objnames:{"0":["py","module","Python \u6a21\u5757"],"1":["py","function","Python \u51fd\u6570"],"2":["py","class","Python \u7c7b"],"3":["py","method","Python \u65b9\u6cd5"],"4":["py","attribute","Python \u5c5e\u6027"],"5":["py","exception","Python \u4f8b\u5916"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:attribute","5":"py:exception"},terms:{"0020":43,"02":[12,17],"03":17,"04":[12,17],"05":17,"06":17,"07":17,"08":[12,17],"09":17,"0x10a8ba860":14,"10":[2,4,10,12,17,29,38,43,45],"100":[3,4,23,44],"106":41,"109":41,"11":[17,43,44],"112":41,"113":[10,41],"114":41,"11th":4,"12":[8,17,43,44],"123":7,"126":41,"127":44,"128":36,"13":[41,43,44],"14":[17,43],"141":41,"142":41,"146":41,"147":41,"15":[17,43],"159":41,"16":[11,17,43,44],"160":41,"17":[11,41,43,45],"174":41,"178":41,"18":[11,17,43],"180":41,"183":41,"184":41,"187":41,"19":[17,43],"191":41,"192":41,"193":41,"198":41,"1990":11,"20":[10,12,17,43,44],"2017":[17,43],"2018":[12,17],"2019":17,"202":41,"2020":[16,17],"204":41,"21":[17,43],"213":41,"215":41,"216":41,"21s":44,"22":[41,43],"224":41,"225":41,"228":41,"23":[12,17,43,44],"231":41,"24":[17,43],"249":3,"25":[17,43],"258":41,"26":[17,43],"261":41,"265":41,"27":[41,43],"275":41,"279":41,"28":[17,43],"280":41,"281":41,"282":41,"287":41,"288":41,"289":41,"29":[41,43],"291":41,"297":41,"298":41,"30":[41,43],"302":41,"304":41,"305":41,"307":41,"308":41,"309":41,"31":[41,43],"310":41,"313":41,"32":[23,36,41,43],"323":41,"32c0feba61ea":44,"32c0feba61ea_add_users_t":44,"33":[41,43],"333":41,"334":41,"335":41,"34":41,"351":41,"365":41,"378":41,"38":41,"382":41,"387":41,"39":41,"393":41,"395":41,"396":41,"3apull_request":8,"40":7,"400":38,"401":41,"402":41,"403":41,"404":44,"406":41,"407":41,"408":41,"411":41,"42":23,"425":41,"427":41,"43":41,"431847":12,"433":41,"437":41,"440":41,"441":41,"447":41,"451":41,"457":41,"47":41,"478":41,"486":41,"487":41,"4888":43,"4c59ad":41,"504":41,"518":41,"520":41,"53010":44,"53015":44,"533":41,"538":41,"54":44,"5432":[8,38,39,44],"5433":8,"567":41,"569":41,"573":41,"577":41,"579":41,"582":41,"585":41,"59":41,"592":41,"599":41,"60":41,"600":[8,41],"609":41,"627":43,"628":41,"629":41,"63":41,"63562":44,"63563":44,"637":41,"638":41,"64":36,"655":41,"659":41,"660":41,"661":41,"662":41,"667":41,"67":41,"672":41,"673":41,"674":41,"693":41,"694":41,"695":41,"696":41,"73":41,"75":41,"76":41,"79":41,"80":[41,44],"8000":44,"84":41,"87":[41,43],"89":41,"90":41,"abstract":[4,24,33,41],"boolean":[11,29,33],"break":[1,3,15,41],"case":[2,3,4,6,10,12,13,14,15,21,26,41],"class":[2,6,7,10,11,12,13,14,21,22,23,24,26,27,28,29,32,33,34,35,36,38,41,43,44,45],"default":[2,3,4,6,7,8,11,12,13,14,15,19,21,23,24,26,27,29,32,33,38,39,41,44,45],"do":[1,2,3,4,5,6,8,12,15,29,38,39],"enum":[26,27,41],"export":[6,8],"final":[2,4,10,29,41],"float":[11,26],"for":[0,1,2,4,5,6,7,8,11,12,14,15,19,21,22,23,24,26,27,29,31,33,35,36,38,39,41,43,44,45],"function":[2,8,11,20,22,26,27,29,33],"if":[2,3,4,6,7,8,10,11,12,14,15,20,21,22,23,24,26,27,29,33,35,36,38,39,41,44],"import":[2,3,4,6,7,10,11,12,13,14,20,21,24,31,33,35,38,39,41,43,44,45],"in":[1,2,3,4,5,6,7,8,11,12,13,14,15,19,21,22,23,24,26,27,29,33,36,38,39,41,43,44],"int":[11,15,38,44],"long":[1,2,3,4,15,29,38,39],"new":[2,3,4,6,7,8,10,12,14,21,22,23,24,26,29,33,35,38,41,45],"null":[2,41],"public":14,"return":[2,3,4,7,10,11,12,14,15,21,22,23,24,26,27,29,33,35,38,39,41,44,45],"short":[2,3,4,7,14],"static":28,"super":[4,12],"switch":[2,3,21,41],"transient":2,"true":[2,3,4,6,7,10,11,12,14,15,21,23,24,26,27,28,29,33,34,38,39,41,43,44,45],"try":[2,3,4,10,15,29,36,41],"var":8,"while":[1,2,4,10,12,14,21,23,29,36,41],"with":[1,2,3,4,5,6,8,12,14,15,17,19,21,22,23,24,26,29,33,36,37,41,43,44,45],__:44,__all__:41,__attr_factory__:24,__init__:[12,44],__main__:38,__metadata__:24,__model__:41,__name__:[24,38,44],__repr__:38,__table__:[14,23,24],__table_args__:[24,41,45],__tablename__:[6,7,10,11,12,14,24,38,41,43,44,45],__values__:24,_base:27,_bind:10,_child:12,_children:12,_engin:[26,27],_event:[26,27],_floattyp:26,_idx1:45,_idx2:45,_integertyp:26,_is_metadata_oper:34,_lastrowid:26,_matchtyp:26,_name_idx:10,_numerictyp:26,_parent:12,_pk:45,_sa:26,_schematranslatemap:29,_test:44,_update_request_cl:41,abandon:41,abcd:8,abil:[12,41],abl:[3,4],abnormal_detect:11,abort:38,about:[1,2,4,6,10,14,15,22,23,29,41],abov:[2,3,4,7,10,12,33],absolut:[2,4],accept:[2,3,7,21,23,26,27,29,41],access:[0,2,3,10,14,15,24,29,36,38,41],access_log:11,accessor:26,accid:2,accord:[3,4,29,33,39],achiev:[10,12,14,21,29],acid:3,acquir:[2,3,4,14,21,26,27,28,29,36,38,41],acquisit:4,across:[7,33],act:2,action:[8,36],activ:[7,8,29,44],actual:[2,3,4,7,12,14,15,23,24,29,38],adapt:3,add:[2,3,4,6,8,10,12,21,22,23,31,41,44,45],add_child:12,add_us:44,added:[3,7,19,21,24,29,41,44],addit:[2,3,7,11,12,33],addition:[4,23],address:[6,14],adjac:10,admin:5,ado:7,adopt:41,advanc:5,advic:4,affect:[3,7,22,23,41],after:[1,2,3,4,6,7,14,15,21,23,24,26,27,29,33,38,41,45],after_get:11,afterward:[26,27],again:[2,3,4,10,12,29,45],against:8,age:[11,23,36],age_idx:11,aggreg:23,aintq:43,aiocontextvar:[2,5,17,18,19,41,43],aiohttp:[41,43],aiomysql:[17,18,19,25],aiomysqldbapi:26,aiomysqldialect:26,aiomysqlexecutioncontext:26,aiomysqliter:26,alemb:[5,11,14,21,42,43,45],alembic_sampl:6,ali:10,alia:[10,12,23,33,41],aliasload:33,alik:41,all:[1,2,3,4,6,7,8,10,11,12,14,15,21,23,24,26,28,29,33,36,38,39,41,45],all_us:45,allow:[2,3,14,21,24,26,27,41],alon:2,alpha:[10,41],alpin:[8,44],alreadi:[3,4,12,24],alright:1,also:[2,3,4,6,7,10,12,14,15,21,22,23,24,26,27,29,31,33,41],altern:[2,10,13,29],although:[3,12],alwai:[2,3,6,8,10,14,21,23,29,33,36,38,41],amaz:10,among:3,amount:2,an:[1,2,3,4,7,8,10,12,13,14,15,21,22,23,24,26,27,29,31,33,36,38,41,43],and:[2,3,4,5,6,7,8,11,12,13,14,15,21,22,23,24,26,27,29,31,33,35,36,38,39,41],andrei:43,ani:[2,3,4,6,10,21,24,26,27,29,31,33,41],anoth:[2,4,12,22,23,29,33],answer:[3,10],anyth:[4,14,29,41],anywai:3,api:[0,2,4,5,10,14,17,19,22,26,27,29,36,42,43,45],apirout:44,apk:44,app:[4,10,13,38,39,41,44],appear:3,append:23,append_where_primary_kei:23,appli:[2,3,5,14,23,29,36,43,45],applic:[4,7,10,21,39,41,44],appreci:8,approach:[3,4,7,10],arbitrari:4,archlinux:43,are:[1,2,3,4,5,8,10,11,12,13,14,15,21,23,24,29,33,36,38,41,45],arg:[19,21,23,24,26,27,28,29,34,36],argument:[2,3,7,10,12,19,21,22,23,26,27,29,33,35,39,41],around:[3,29,43],arq:43,arrai:[11,27,41],arrayproperti:[11,32],arriv:1,arrrrh:1,articl:3,as:[2,3,4,6,7,10,11,12,13,14,15,21,22,23,24,26,27,29,33,35,36,38,41,44,45],ascend:12,ascii_lett:[10,12],asgi:44,ask:2,assembl:[2,12],assert:[15,24,36,41,44,45],assertionerror:41,assign:[2,26],assist:12,associ:[2,14],assum:[3,4,7,44],assumpt:[3,36],async:[0,1,2,4,7,10,12,14,15,21,23,26,27,28,29,34,35,36,38,41,43,44,45],async_execut:[26,27,28],async_main:3,asyncdialectmixin:[26,27,28],asyncenum:[26,27],asynchron:[0,2,3,12,14,15,21,29,36],asyncio:[0,2,3,5,12,14,16,20,43,45],asyncpg:[2,3,4,10,13,14,15,16,17,18,19,25,29,35,39,41,43,44,45],asyncpg_deleg:41,asyncpgcompil:27,asyncpgcursor:27,asyncpgdbapi:27,asyncpgdialect:[3,27],asyncpgexecutioncontext:27,asyncpgiter:27,asyncpgjsonpathtyp:27,asyncpgsa:[14,43],asyncschemadropp:34,asyncschemagener:34,asyncschematypemixin:34,asyncvisitor:34,at:[2,3,4,7,8,10,12,14,15,21,23,29,38,39,41],ath:23,atom:23,attent:38,attribut:[3,10,12,14,23,24,29,33,41],attributeerror:21,audit_profil:11,aur:43,austin:10,auth_plugin:26,authent:4,author:[4,21,43,44],author_id:43,auto:[0,11,23,24],autocommit:[0,4,26],autogener:[6,44],autom:12,automat:[3,7,12,20,22,23,24,29,33,35,36,38],avail:[2,5,10,14,15,23,24,29,36,39,41],averchenkov:41,avoid:[3,4,33],awai:[2,4],await:[1,2,3,4,7,10,11,12,14,15,21,23,29,33,36,38,39,41,43,44,45],await_onli:3,awesom:[1,43],back:[2,3,4,12,15,21,29,36,41],backend:44,background:3,backport:[2,10,41],backward:41,bad:[3,41],bake:[7,19,21,22,41],baked_queri:[26,27,28],bakedqueri:[5,22],bakeri:[5,17,18,19,21,26,27],balanc:[4,23],barancsuk:41,bare:[4,5],base:[3,6,10,12,13,17,18,19,21,23,24,25,26,27,33,44],base_exp:32,basedbapi:[26,27,28],baseexcept:[15,36],basemodel:44,basic:[2,3,4,5,23,43],batch:[5,23],bayer:[4,43],be:[0,1,2,3,5,6,7,8,12,13,14,15,21,22,23,24,26,27,29,31,33,36,38,39,41],becaus:[2,3,4,7,10,14,15,21,23,36,41],becom:[2,3,14],been:[2,26,27,41],befor:[1,2,3,4,7,8,10,12,14,15,22,23,26,29,36,38,45],before_set:11,begin:[3,4,7,26,27,28],begun:3,behav:[23,41],behavior:[2,3,10,15,23,29,38,41],behind:[2,3,4,7,12,23,41],being:[2,3,4,14,23],belong:15,below:[8,10,11],benefici:4,besid:10,best:[8,41],beta:41,better:[4,41,43],between:[2,4,41],beyond:4,biginteg:[21,38,44],bin:44,binari:[28,41],bind:[2,4,7,10,14,15,21,22,23,26,27,34,41],bind_processor:[2,27],bindparam:7,bindtempl:27,binghan:41,birthdai:11,bit:[8,14,23,41],bite:[29,38],black:41,blob:[43,44],block:[2,3,4,15,29,36,38],blue:2,bondar:43,book:[10,21,43,45],booker:45,bookings_idx_booker_room:45,bookings_idx_day_room:45,bookings_pkei:45,bool:[11,44],booleanproperti:[11,32],boost:7,borrow:[2,15,29,38,39],boss:1,bot:43,both:[2,3,4,10,12,14,21,23,24,26,27,36,41,45],bottleneck:4,bound:[21,23,24,26,38],boundari:3,branch:8,bridg:3,brien:41,broken:41,browser:8,bryanforb:43,bsd:16,buffer:[3,4],bug:[3,8,10,41,43],bugfix:8,build:[3,4,8,10,12,21,23,44],builder:[12,44],built:[3,10,14,22,23,24,29,39,41],builtin:[28,38,41],bulk:[4,5,29],bunch:6,bundl:21,busi:[4,14],but:[1,2,3,4,7,10,12,14,15,19,21,22,23,24,29,33,41],by:[2,3,4,7,8,10,12,14,15,21,22,23,24,26,27,29,33,36,38,39,41,43],bypass:3,c10k:1,cach:[7,44],calcul:26,call:[2,3,4,7,10,12,15,20,21,23,24,26,27,29,33,36,41],call_next:10,callabl:[12,26,27,29,33,41],callableload:33,caller:10,came:4,can:[1,2,3,4,5,6,7,8,12,13,14,15,21,22,23,24,29,33,36,38,39,41],candid:41,cannot:[2,3,12,41],canopi:43,canopytax:43,cap:4,care:3,cast:[11,44],categori:12,categories_1:12,categories_2:12,caught:36,caus:[2,3,4,15,29,38,39,41,45],cd:[8,44],cdi:43,celeri:4,certain:[4,38],chain:[2,4,7,21,23,33,41],challeng:4,chanc:4,chang:[3,6,8,10,23,29,41,44],characterist:3,charset:26,chat:4,check:[2,3,8,10,12,14,23,26,27],checkfirst:[26,27,34],checkout:8,child:[12,41],child_id:12,children:[12,36],chmod:8,choic:[4,10,12],choos:[4,19,41],ci_:44,classic:12,classmethod:[7,23,28,33],claus:[2,10,12,14,21,23,26,27,28,29,33],clean:[10,41],cleaner:3,cleanli:[4,29],cleanup:39,clear:4,click:2,client:[4,8,43,44],client_flag:26,clone:8,close:[2,3,4,15,21,22,26,27,28,29,36,38,41,45],closer:3,cls:[7,11,24],cmd:44,cn:[8,43],code:[2,3,4,10,12,14,15,29,36,41,45],coin:4,col:12,collect:[23,44],collid:41,color:[2,26,27,28,29],colspec:[26,27],coltyp:[26,27],column:[2,5,6,7,11,12,14,21,23,24,26,27,29,33,38,41,43,44,45],column_kei:27,column_nam:23,columnattribut:24,columnload:[10,12,33],com:[8,10,16,43,44],combin:[2,12],come:14,command:[3,4,6],command_timeout:27,comment:[3,7,29],commit:[0,4,8,15,26,27,28,29,36,41,44],common:[7,14,15,29,39],commun:[3,43],compani:33,compar:[4,23,44],compat:[2,3,10,29,33,41],compil:[7,21,22,28,29],compiled_sql:22,complet:[2,10,21,41,44],complex:[5,7,12,43],compli:3,compliant:3,complic:0,compos:44,composit:23,concentr:4,concept:[2,14],conceptu:14,conclus:4,concret:[2,14,24,38],concurr:[3,4],condit:[12,23],config:[10,13,38,39,41,44],configur:[8,17,33,37,38],confirm:41,conflict:[3,12,33],conftest:44,confus:2,congratul:6,conn1:2,conn2:2,conn:[2,3,4,14,21,26,27,28,29,36,41],connect:[0,3,4,5,7,12,13,14,15,17,19,21,22,26,27,29,34,36,37,38,41,44],connect_timeout:26,connection_cl:29,connection_class:27,connectionless:2,conradi:41,consid:[3,4,10,41],consist:41,constantli:[3,7],constraint:[24,34,41],construct:[4,7,10,14,21,24],consum:[3,4],contain:[3,6,10,24],content:[17,18],context:[2,3,4,10,15,21,22,26,27,28,29,33,36,38,39,41,43,44],contextu:[10,12,22,38],contextualgino:10,contextvar:[2,10,17,20,43],continu:[15,36],contrast:4,contribut:[5,41],control:[3,5,26,27,29,38,41],conv:26,conveni:[2,3,4,14,15,21,23,43],convers:[2,26,27],convert:33,cool:1,cooper:4,cooperative_multitask:1,copi:[3,10,29,41,44],core:[2,3,5,7,10,16,21,22,24,41,43,45],coroutin:[1,2,3,4,23,29,35],correctli:[3,4,14,15,21,23,41],correspond:[2,12,22,23,24,26],correspondingli:[2,15,36],could:[2,3,4,7,10,12,22,23],count:[2,10,12,45],count_1:45,cov:44,cover:41,coverag:41,cpu:1,cpython:43,creat:[0,3,4,5,7,8,10,12,14,15,19,21,22,23,24,26,27,29,33,34,35,36,38,39,41,43,44,45],create_al:[3,7,10,11,12,14,21,34,41,45],create_async:[26,27,34],create_async_engin:3,create_engin:[2,3,4,7,10,13,14,19,21,26,29,33,35,41,45],create_ok:34,create_pool:[2,41],create_t:44,create_task:10,createdb:[44,45],creation:[2,7,10,21,22,41],credenti:6,credit:8,cross:38,crt:8,crucial:15,crud:[2,4,10,12,14,17,18,19,21,29,41,43],crudmodel:[21,23,41],csrf:44,ctrl:44,ctx:12,cur:44,curatedlist:43,current:[2,3,10,13,15,19,23,26,27,29,41],current_connect:[0,29,41],current_databas:10,current_us:[4,43],cursor:[2,3,26,27,28,29],cursor_cl:[26,27,28],cursorclass:26,custom:[3,5,11,12,23,24,29,41],customiz:[24,41],cut:4,cutil:41,cve:44,dahlia:43,dai:45,daisi:[11,24,43,45],damn:1,danger:38,darwin:44,data:[2,3,10,11,12,23],databas:[0,2,3,5,6,7,8,11,12,14,15,19,21,23,24,26,27,29,33,36,38,39,41,43,44],datastructur:44,date:45,datetim:[10,11,12,24,33],datetimeproperti:[11,32],db:[0,2,4,5,7,8,10,11,12,13,14,15,21,23,24,26,27,29,34,36,38,39,41,43,44,45],db_age:23,db_databas:[38,44],db_driver:44,db_dsn:44,db_echo:[10,38,41,44],db_host:[13,38,44],db_kwarg:[13,38],db_name:[6,8],db_pass:8,db_password:[38,44],db_pool_max_s:[38,44],db_pool_min_s:[38,44],db_port:[8,38,44],db_retry_interv:44,db_retry_limit:44,db_ssl:44,db_time:7,db_use_connection_for_request:[38,44],db_user:[8,38,44],dbapi:[26,27,28],dbapi_class:[26,27,28],dbapi_conn:[3,26,27],dbapicursor:[26,27,28],dbname:[10,44],ddl:[34,44],dead:4,deadlock:[3,4],deal:[4,10,12,15],debug:38,decent:3,decid:3,decim:41,declar:[12,14,17,18,19,21,23],declarative_bas:24,declared_attr:[7,11,24,41,45],decod:32,decor:[7,24],deeper:3,def:[1,2,3,4,7,10,11,12,14,24,26,27,38,44,45],default_isolation_level:26,defaultdialect:[26,27],defer:4,defin:[3,5,6,7,11,12,13,14,21,24,29,33],definit:[10,12,24],del:10,delai:4,deleg:[2,14,21,23,41],delet:[12,14,23,41,44,45],delete_us:44,deliv:3,demand:7,demo:44,depend:[2,4,6,7,10,23,29,36,41,44],deploi:4,deprec:[3,33,41],describ:[4,10],descript:[6,8,26,27,28,39,44],design:[2,3,4,10],detail:[2,8,10,12],detect:44,determin:[4,12],deutel:41,dev:[43,44],develop:[6,8,17,38],diagram:2,dialect:[2,3,10,11,13,17,18,19,22,29,34,36,39,41],dict:[2,10,11,13,23,24,33,41,44],dictionari:[2,29,38],did:41,didn:[2,4,31],differ:[2,3,4,5,11,12,14,21,23,24,29,41],dig:14,direct:[4,26,29],directli:[2,3,4,7,10,12,14,21,23,24,26,29,38,41],directori:[6,41],dirti:[10,41],disabl:[23,41],disable_inherit:41,disable_task_loc:41,disadvantag:4,disallow:10,disast:[15,38],discard:[2,23,29],discord:43,discourag:4,discuss:10,disproportion:4,dist:41,distinct:[12,23,33,41],distinguish:2,divio:16,django:5,do_load:33,do_on_connect:[26,27],doc:[3,6,8,10,15,41,43,44],docker:[8,44],dockerfil:44,docstr:8,document:[2,3,6,7,10,41,43],doe:[2,3,4,5,12,14,21,23,29,33,41,43],doesn:[2,3,4,10,11,12,24,41],doge:3,doing:[4,7,21,41],don:[0,2,3,5,10,12,15,23,29],done:[0,1,2,6,8,10,14,15,22,41,44],doubt:12,down:[4,44],downgrad:[6,44],driven:8,driver:[2,3,10,15,26,27,29,39,41],drivernam:44,drop:[34,41],drop_al:[3,10,12,34],drop_async:[26,27,34],drop_ok:34,drop_tabl:44,dsn:[39,41,44],due:[4,41],dure:[2,22,24,29,38,41],dynam:[12,14,24],dziewulski:41,each:[2,3,4,6,7,10,12,22,24,29,33,38],earli:[3,15,17,36,39],earlier:[3,10,39],easi:[2,3,10,12],easier:[3,7,10],easili:[2,3,4,12],echo:[2,3,10,29,39,41,44],ecosystem:[3,4],edit:11,effect:[19,23,29],effici:2,effort:4,either:[2,3,4,6,7,10,14,15,23,29,36],elem:[21,22,28],element:21,els:[2,3,4,10,14,15,29,38,44],email:[4,10],email_address:14,emerg:41,emit:3,empti:[2,38,39,41],en:[1,43],enabl:[2,8,10,23,24,33,39,41],enable_inherit:41,enable_task_loc:41,encapsul:[3,4,10],encod:[32,43],encourag:21,encrypt:8,end:[2,3,4,12,15],endpoint:4,enforc:[3,12,15],engin:[0,3,5,15,17,18,19,21,22,23,26,35,36,39,43,44,45],engine_cl:35,engine_from_config:21,enginestrategi:35,enhanc:[4,41],enjoi:38,enough:4,ensur:41,enter:[15,21,29],entri:[14,31,44],entry_point:44,enumer:26,env:[6,10,44],environ:[8,41,44],ep:44,equal:[2,23,41],equival:[23,24,26],error:[3,10,27,28,41],es:24,especi:[2,4,10,12,41],establish:38,etc:[3,7,26,27],even:[2,3,4,7,10,14,21,23,24,29,38,41],event:[1,3,4,8,26,27,41,43],eventlet:43,eventu:[2,3,10],ever:[3,36],everi:[7,8,12,23],everyon:2,everyth:[2,3,4,10,14,15,38],evil:3,exactli:[2,12,21,29],exampl:[2,3,4,6,7,8,10,12,13,14,15,21,23,29,33,36,38,41,45],except:[1,2,3,10,15,17,18,19,27,28,29,36,38,39,41],exchangeratesapi:43,excit:3,exec:8,execut:[0,3,4,5,7,11,12,14,21,22,23,26,27,28,29,33,39,41,45],execute_bak:[26,27,28],executemani:[28,29,41],execution_ctx_cl:[26,27],execution_opt:[2,3,7,10,12,21,22,23,26,29,33,41],executioncontextoverrid:[26,27,28],exhaust:[2,4],exist:[2,3,4,5,21,23,24,26,27,39,41],exit:[15,21,29,36],exp:11,expect:[3,33],experiment:[12,23,33],explain:[3,4,10,12],explan:43,explicit:[0,2,3,10,12,15,26,41,43],explicitli:[2,3,4,7,10,11,36,38],explict:14,expos:[14,21,41],exposur:3,express:[5,11,23,29,33],ext:[3,10,13,17,18,19,21,38,39,41,44],extens:[2,7,10,13,14,21,31,38,39,41],extra:[3,33,41,44],extract:41,extrem:2,f2c273a43a3ed893a767d4239046f2befabf510d:43,face:4,facebook:43,facil:26,fact:[3,10,26,27],factori:[22,24,39],fail:[2,21,41,44],fals:[2,3,4,7,11,14,23,24,26,27,28,29,34,38,39,41,44],fantix:[23,43,44,45],far:[3,14],fast:[4,38,43],fastapi:[4,10,42,43],faster:[7,22],featur:[2,3,5,8,12,23,33,41],fed:[7,12,41],feed:[29,33],feedback:8,feel:[3,23,29],fetch:29,fetchal:3,fetchval:3,few:[2,4,10,29,38,41],field:[10,11,14,23],file:[1,6,8,41,44],fill:33,filter:23,find:[3,4,6,24,31],fine:[3,4,10,14],finish:[2,4,6,23,29,38,44],first:[2,3,4,5,7,8,10,12,14,19,21,23,26,27,28,29,33,38,39,41,45],first_connect:[26,27],first_nam:10,first_or_404:41,firstli:12,fit:2,five:41,fix:[3,4,10,41],fixtur:44,flag:[26,27,39],flake8:8,flat:10,flexibl:[11,12,43],flow:4,flush:4,fly:5,focu:10,folder:6,follow:[2,3,4,10,11,15,21,33,36],forc:4,foreign:[10,12,23],foreignkei:[10,12,14,43],forev:[3,23,29],forget:[2,3],fork:8,format:[10,26,38],former:2,fortun:4,forward:[12,26,27,28],found:[2,4,10,12,21,22,24,41,45],foundat:[4,43],founder:45,founding_us:45,fouser:21,framework:[7,10,41],free:[4,23,29],freebsd:43,freez:4,frequent:12,friendli:41,from:[2,3,4,6,7,10,11,12,13,14,15,21,22,23,24,29,33,36,38,39,41,43,44,45],fromclaus:33,frustrat:3,full:[2,6,10],full_path:6,fulli:29,fullnam:14,fun:4,func:[10,12,33,45],func_or_elem:[21,22],fundament:[3,4],further:[2,3,7,14,29,45],furthermor:[2,7,29,38],fut:10,futur:[3,10,12],gain:12,galden:41,garciasilva:43,gbasic:43,gcc:44,gener:[2,11,12,23,24,26,27,33,44],geoalchemi:43,get:[2,3,4,5,7,10,12,14,19,21,22,23,24,29,33,38,41,43,44,45],get_affected_row:[26,28],get_app:44,get_column:33,get_current_connect:41,get_event_loop:45,get_from:33,get_isolation_level:[26,27],get_lastrowid:[26,28],get_loc:41,get_now:2,get_or_404:[38,41,44],get_profil:32,get_raw_connect:29,get_result_proxi:28,get_statusmsg:[26,27,28],get_us:[38,44],get_vers:19,getattr:44,getlogg:44,getter:[7,21],gevent:43,gino:[2,3,4,5,6,8,11,12,13,15,17,18,38,39,42],gino_db:8,gino_fastapi_demo:44,gino_fastapi_demo_test:44,gino_starlett:31,ginoconnect:[2,14,15,17,22,29,36],ginoengin:[2,10,14,15,21,22,23,29,33,35,36,41],ginoexcept:30,ginoexecutor:[2,7,21,22],ginonulltyp:[26,27],ginopool:41,ginoschemavisitor:[14,21,34],ginostrategi:35,ginotransact:[15,29,36,41],git:[8,29,44],github:[8,10,16,43,44],gitignor:44,gitlab:43,gitter:16,give:[1,3,4,12,22,33],given:[2,3,8,10,12,21,22,23,24,26,27,29,33,35],global:[7,14,21],gmail:44,gnu:43,go:[1,6,14],goe:45,going:3,golden:4,goncharov:41,gone:41,good:[1,3,29],got:1,gotta:1,grab:3,grai:2,grammar:[4,10,41],grandson:12,grant:3,great:[4,6,10],greater:20,greatli:[2,3,8],green:2,greenlet:[3,10],greenlet_spawn:3,group_bi:[10,12],guarante:[4,15,29],guess:4,guess_model:41,guidelin:5,gunicorn:44,hack:10,had:[14,24],hand:2,handl:[2,4,15,29,36,41],handler:38,hang:[3,4],happen:[3,4,21,29],hard:4,hardwar:4,harm:4,has:[2,3,4,7,10,12,14,15,19,21,22,23,26,27,29,41],has_schema:27,has_sequ:27,has_tabl:[26,27],has_typ:27,hash_:22,haunt:[3,4],have:[1,2,3,4,6,7,10,12,14,15,23,33,36,39,43],head:[2,6,44],heavi:3,hei:[1,4],height:11,help:[0,8,15],helper:7,henc:21,here:[1,2,3,4,7,8,10,11,12,14,15,23,29,36,41,44,45],herebi:21,hidden:[2,3],hide:[2,41],hierarch:[10,12],high:[4,7],hit:4,hmm:2,hold:[1,3,10,15],holubakha:41,hong:43,hood:[2,23],hook:[5,10,21,26,27,31],hoorai:3,host:[26,27,39,44],how:[0,2,3,5,6,8,12,15,29,43],howev:[2,3,4,10,14,21,23,29,41],html:43,http:[1,6,8,10,16,29,43,44],hurri:1,hybrid:4,id:[6,7,10,11,12,14,21,23,24,26,33,38,43,44,45],id_val:10,ide:43,idea:[3,4],ident:[2,3,12,14,23,41],identifi:4,idl:[3,4],ids:12,ignor:22,ila:41,im:16,imagin:3,immedi:[2,4,7,19,23,29,36],immut:2,impl:44,implement:[2,3,24,26,33],implic:14,implicit:[0,3,4,10,15,21,26,41,43],implicitli:[2,4,7,10,14,15,29,36],importantli:4,importlib:44,imposs:4,improv:41,in_:12,in_queri:23,inc:43,includ:[3,8,10,23,39,41],include_foreign_key_constraint:34,include_rout:44,increment:23,index:[5,12,24,34,43,45],index_on_nam:10,indic:[24,33],individu:[2,3,23],infer:23,info:44,inform:[2,7,10,14,15,21,22,29],inherit:[2,7,10,15,22,23,29,41],ini:[6,44],init:[6,26,27,41,44],init_app:[13,38,39,44],init_command:26,init_kwarg:[26,27],init_pool:[26,27,28],initi:[2,3,5,7,11,14,23,24,26,27,33,35,39],initializederror:30,inject:[2,14],inlin:[14,21,27,41],inner:[2,15,26,27],ins:14,insert:[3,5,14,15,23,26,29,41,43,45],insid:6,insist:12,inspir:[12,14],instal:[6,8,39,41,44,45],instanc:[2,4,6,7,10,11,12,14,15,21,22,23,24,26,27,29,32,33,35,41],instant:23,instanti:[14,23,29,33],instead:[2,3,4,12,14,15,23,24,29,31,41],integ:[6,7,10,11,12,14,24,43,45],integerproperti:[11,32],integr:[5,38,41],intend:24,intens:4,interest:3,interfac:[2,3,5,21,33,41],interfaceerror:27,interfer:41,interim:3,intern:[2,10,12,15,22,23,24,36,41],internet:4,interpret:[12,36],interv:27,into:[2,3,4,7,8,10,11,12,14,15,22,23,26,27,29,31,33,41,45],introduc:[3,10,12,41],intuit:4,invalid:38,invent:10,invert_get:24,invertdict:24,invok:[23,26,27],involv:[10,14,26],io:[1,29,43],irrelev:21,is:[1,2,3,4,5,6,7,8,11,12,14,15,16,20,21,22,23,24,26,27,29,33,35,36,38,39,40,41,43],is_:23,is_local_root:41,isdigit:38,ish:4,isol:[0,2,26,27,29,41],isolation_level:[2,3,26],issu:[3,4,8,10,11,26,41,43],it:[1,2,3,4,5,6,7,8,12,14,15,21,22,23,24,26,27,29,33,35,38,41,43],item:[12,14,33,34,44],iter:[2,3,10,12,14,21,23,24,26,27,28,29,41,43],its:[2,3,4,10,12,21,23,24,26,27,29,33,36,39,41,45],itself:[2,3,4,10,12,14,15,24,26,27],iuliia:41,jack:14,java:43,jeff:10,jekel:41,jetbrain:43,jim:41,job:29,join:[5,12,21,23,33,41,43],join_queri:12,join_without_n_plus_1:4,jone:[14,43],json:[5,23,26,27,38,41,44],json_support:[17,18,19,23],jsonb:[11,41],jsonindextyp:26,jsonpathtyp:[26,27,41],jsonproperti:[11,23,32],julio:41,just:[1,2,3,4,6,10,12,14,15,23,24,36,38,41],keep:[3,4,10,12,26,41],kei:[8,10,12,23,24,26,33,41],kentoseth:41,kept:[4,33],keyout:8,keyword:[2,3,7,10,12,19,22,23,33],kill:[1,4],kind:4,king:[43,44],kinwar:41,know:[1,2,3,4,10,15],knowledg:[12,14,23],known:[2,12],ko:41,kooten:41,kovalev:41,kubernet:44,kw:[12,26,27,34],kwarg:[19,21,22,23,24,26,27,28,29,34,35,36,39,41],label:[33,41],lacerda:41,lambda:12,larg:[3,4,29],last:[2,4,12,21,23,36],last_nam:10,lastli:3,lastrowid:26,later:[1,2,10,12,14,29,39],latest:[23,29,43],latter:2,law:4,layer:4,layout:41,lazi:[0,3,10,17,29,37,38,41],lazili:[2,7,19,38],lazy_engin:10,lead:[3,4,11],leaf:12,learn:[2,4,22],least:[4,7],leav:[3,29],led:29,left:[2,3,12,23,43],legaci:14,len:12,length:14,lengthi:4,leosussan:43,less:[2,4,14],let:[1,2,3,4,7,12,14,15],level:[0,2,4,7,10,11,14,23,24,26,27,29],leverag:[3,4,11],li:41,lib:[8,44],libffi:44,librari:[3,41,43],licens:43,liebig:4,lifetim:38,lightweight:6,like:[2,3,4,6,7,8,10,11,12,14,23,24,29,31,38,39],likewis:[2,12,33],limit:[3,4,26,27,28,41],line:[2,6],link:[3,4,6,10],list:[2,8,10,11,14,29,33],listen:[3,4,26,27],liter:29,littl:8,ll:[1,2,4,10,11,12,29,41],load:[2,3,4,5,7,12,21,23,29,33,41,43,44],load_modul:44,loader:[5,10,17,18,19,21,23,28,29,41,43],lobbi:16,local:[2,6,8,17],local_infil:26,localhost:[3,4,7,8,10,12,13,14,33,38,39,44,45],locat:[23,41],lock:[3,4,44],log:44,logger:44,logging_nam:[2,29],logic:[3,4],login:8,longer:[2,4,10,29,41],look:[1,4,12,23,39],lookup:[23,41],loop:[3,10,21,26,27,28,29,35,41,43],lose:29,lost:4,lot:[3,4,12],love:4,low:[4,7],lower:[3,10],luckili:3,made:[3,10,12,14,41],magic:[2,3,7,12],magicstack:[10,43],mai:[2,3,4,10,11,12,15,21,22,26,29,38],main:[2,3,4,6,7,10,12,14,44,45],main_app:6,maintain:[3,4,10,24,43],mainten:[3,41],major:4,make:[2,3,4,6,7,8,10,12,23,26,27,29,38,44],make_express:32,make_url:44,manag:[0,1,3,7,15,21,29,36,38,41],mandatori:14,mani:[2,3,4,5,10,26,27,28,29,41],manipul:41,manual:[3,5,10,11,12,14,23,29,36,41],map:[2,10,12,14,43],mapper:10,marissa:10,mark:[4,24,29],martin:41,masonri:44,massiv:23,master:[43,44],match:[3,12],matchtyp:26,matter:[2,3,12,14],max:[2,44],max_cacheable_statement_s:27,max_cached_statement_lifetim:27,max_inactive_connection_lifetim:27,max_overflow:4,max_queri:27,max_siz:27,maximum:39,maxsiz:26,mayb:3,me:[1,2,3,4],mean:[2,4,12,15,23,24,26,27,29,41],meaningless:[2,24],meant:2,meanwhil:[7,23,36,41],meet:8,member:45,memori:[2,10,23,24,29],mention:[2,3,14],mess:21,messag:[4,41],meta:3,meta_path:31,metaclass:23,metadata:[2,7,10,14,21,22,23,24,34,41,43,44,45],method:[2,3,4,7,14,21,22,23,24,26,27,29,33,36,41],micha:41,michael:43,middl:39,middlewar:[10,39,41],might:[2,3],migrat:[5,17,44],mike:4,mimic:3,min:44,min_siz:[2,27],minhe:43,minim:14,minimum:4,minsiz:26,misc:41,miser:4,miss:[2,3,41],mission:4,mistak:41,mix:[3,4],mixin:[24,41,45],mkdir:44,mkvirtualenv:8,mock:33,mod:44,mode:[2,3,4,26,27,29,36,39],model:[2,4,5,6,7,10,11,14,21,23,24,28,29,33,38,41,42,43,45],model_base_class:21,model_class:[21,24],model_kei:10,modelload:[10,12,33,41],modern:4,modif:45,modifi:[3,12],modul:[2,6,10,17,18,41,44],modulenotfounderror:6,moment:[1,2,4],more:[2,3,4,7,10,12,14,15,21,22,23,24,29,41],morgan:41,most:[2,3,4,10,14,15,23,26,29,41],mostli:[3,4,23],move:[3,41],much:[2,3,4,11,14,15,29],multiparam:[2,21,28,29,41],multipl:[2,5,12,21,22,29,33,41],multipleresultsfound:[29,30],multitask:4,musl:44,must:[2,3,4,6,10,14,21,29,33],mutabl:41,my_app:6,myapp:44,mydb:44,mydb_test:44,mydialect:[26,27],mykyta:41,mymodel:44,myself:[1,4],mysql:[10,26,43,45],mysqlcompil:26,mysqldialect:26,mysqlexecutioncontext:26,mytab:15,mytabl:15,myuser:23,name:[2,3,4,6,7,8,10,11,12,14,21,23,24,33,35,38,39,41,43,44,45],name_or_url:35,namespac:31,nativ:[3,11,41],natur:4,neal:41,neat:4,neath:3,necessari:[4,26,27,38],necessarili:2,need:[1,2,3,4,6,7,8,10,11,12,14,15,19,21,29,38,39,41],neither:22,nest:[2,3,5,12,29,33,36],network:[2,4],never:[4,14,15,29],nevertheless:4,new_child:12,new_nam:10,new_names_dict:10,newer:3,newli:[23,26,27],next:[3,4,6,14,26,27,28,29],nicknam:[6,14,38,44,45],no:[1,2,3,4,6,7,10,12,14,15,19,20,21,23,24,26,27,29,36,41,44],no_delai:26,no_deleg:21,non:[10,13,14,29,41],nonam:[6,14,45],none:[2,3,10,12,14,21,22,23,24,26,27,28,29,32,33,34,35,39,41,44,45],none_as_non:[17,23,33],nor:22,noresultfound:[29,30],normal:[2,3,4,7,11,12,14,15,21,23,24,36],nosuchrowerror:30,not:[1,2,3,4,5,12,14,15,16,21,23,24,26,27,29,33,36,38,41,43],note:[2,3,12,19,26,29,38,41],noth:[1,2,3,4,10,15,33,41],notic:[2,12],now:[1,2,3,4,6,7,8,10,12,14,15,21,29,33,41],nullabl:[11,14,44],nullpool:[13,27,41],nulltyp:[26,27],number:[2,4,11,23,39],numer:[26,28],obj:34,object:[2,3,4,6,7,10,11,12,14,21,22,23,24,26,27,28,29,32,33,34,36,41,43,44],objectproperti:[11,32],obviou:4,obvious:[4,41],occasion:[4,21],occur:[26,27],odd:3,of:[1,2,3,4,5,6,7,11,14,15,21,22,23,24,26,27,29,31,33,36,38,39,41,43],off:[2,3,4,38,41],offer:[2,14,36,41],offici:[6,21,31],often:[21,23,24],oh:1,ok:3,okai:1,olaf:41,old:[3,23,43],olexii:41,omit:[12,23],on:[1,2,3,4,5,8,12,14,15,21,22,23,24,26,29,33,36,38,39,41,43,44],on_claus:[23,33],on_connect:[26,27],onc:[2,4,6,10,12,22,24,26,27,29],one:[1,2,3,4,5,6,7,10,14,21,23,26,27,28,29,33,41],one_or_non:[2,7,14,21,29,41],ones:[2,3,4,21,33],onli:[2,3,4,6,7,12,13,14,15,21,22,23,24,26,29,33,36,39],ons:2,op:[3,20,44],open:[3,6,8,15,29,43],openid:4,opensourc:43,openssl:[8,44],oper:[2,4,21,23,24,29,41,43],opposit:[4,29],ops:3,opt:29,optim:2,option:[2,3,7,10,12,13,21,22,23,26,27,29,33,41],or:[2,3,4,5,6,7,8,12,14,15,20,21,22,23,24,26,27,29,33,35,36,38,39,41,44],order:[2,3,10,12,23,29],ordinari:12,org:[1,6,43],origin:[3,8,14,23],orm:[0,2,3,5,12,16,43,45],orphan:2,orz:43,os:[1,3,4],oss:43,other:[2,3,4,5,6,8,13,14,15,21,23,24,29,33,39,41],otherwis:[24,26,27,29],our:[4,6],out:[1,2,4,8,14,24,29],outer:[2,12,15,23],outerjoin:[10,12,33,41,43],output:[3,12,14],over:4,overal:[4,12],overhead:4,overload:4,overrid:[7,21,23,24,41],overwrit:3,own:[2,4,10,12,13,14],owner:8,packag:[6,17,18,39,41,43],pai:38,pair:[12,23],parallel:2,param:[21,28,29],paramet:[2,5,7,14,24,27,28,29,33,39,41,45],paramref:26,paramstyl:[2,26,28],parent:[2,10,12,14,21,36,41],parent_id:[10,12,41],parents_x_children:12,parentxchild:12,pars:[29,41],part:[2,3,4,10,26],partial:[2,10,41],particular:[26,27],particularli:23,pascal:41,pass:[7,8,26,27,29,33,39,41,44],passfil:27,passin:8,passiv:41,passout:8,password:[6,8,26,27,39,44],patch:[10,20,41],patch_asyncio:20,patch_schema:34,pattern:[4,10,12],pavol:41,payload:2,pem:8,pend:[3,17,23],peopl:[3,4,12],pep:[3,41,43],per:[3,26,27,29],perform:[4,7,10,38],perman:[2,3,29,39,41],persist:3,person:3,peter:43,pgcompil:27,pgdialect:[3,27],pgexecutioncontext:27,pgjone:43,philip:43,pictur:3,piec:12,pip:[6,8,39,41,44,45],place:[3,24,26,29,41],plai:[2,4,21,29],plain:[2,10,43],plain_old_java_object:43,platform:[4,44],pleas:[1,2,3,7,10,11,12,14,15,19,21,29,38,41],plu:[7,21],pluggi:44,plugin:44,poetri:[6,41,44,45],point:[3,4,29,31,44],poli:24,pool:[2,3,4,7,13,22,26,27,28,29,38,39,41,44],pool_class:[13,26,27,28],pool_max_s:[39,44],pool_min_s:[39,44],pool_recycl:26,poolev:[26,27],pop_bind:[2,21,41,45],popo:43,popul:[23,45],port:[3,4,14,26,27,39,44],posit:[7,23,26,27,29,33],possibl:[2,3,4,6,7,12,14,22,26,27,33,38,41],post:[2,3,12,44],post_exec:26,postfetch_lastrowid:26,postgi:43,postgr:[6,8,10,35,38,39,44],postgreserror:27,postgresql:[2,3,4,7,8,10,11,12,13,14,21,27,33,35,36,41,43,44,45],postgresqlimpl:44,postprocess:29,potenti:[3,12],pr:[43,44],practic:[3,4,29],pre:[3,19,22],prebak:[7,19,26,27],preexecute_autoincrement_sequ:26,prefer:[4,26,33,38],prefetch:41,prefix:35,prepar:[5,6,19,22,26,27,28,29,41],preparedstat:[27,28],present:[2,12,29,41],press:44,pretti:[4,14],prevent:[7,41],previou:[2,12,14,21,39,41],previous:[2,12],primari:[23,26,41],primary_kei:[6,7,10,11,12,14,21,24,38,43,44,45],primarykeyconstraint:[44,45],primit:3,princip:[3,4],print:[3,5,7,11,12,14,23,43,45],prioriti:4,privkei:8,probabl:[2,3],problem:[3,4,6,41],proce:[26,27],process:[2,12,26,27,41,44],process_row:28,processor:[12,41],procur:26,produc:[2,12],product:0,profil:[11,32,41],program:[2,3,4,12,15],program_nam:26,progress:2,prohibit:15,project:[6,8,10,44],promis:12,prop_nam:[11,24,32],propag:15,proper:[3,4,22],properti:[2,4,5,6,10,12,14,21,22,26,27,28,29,33,36,41],protect:41,provid:[2,3,7,10,11,12,14,15,21,22,23,29,31,33,39,41],proxi:[23,26],psql:8,psycopg2:[2,3,44],psycopg:44,publicli:[21,29],pull:5,pure:[10,41],push:8,put:[2,3,8,14,36],pwd:8,py:[6,8,10,41,43,44],pycharm:43,pydant:44,pypi:[16,41],pyproject:44,pytest:[8,44],python:[1,2,3,4,6,8,10,11,16,20,24,31,41,42,44,45],pythongino:43,pythonpath:[6,44],qbasic:43,qualiti:4,quart:[41,43],queri:[0,4,5,7,8,11,12,14,17,19,21,22,23,24,26,27,28,29,33,39,43,45],query_cl:22,query_executor:21,query_ext:21,query_param:10,querymodel:23,question:[3,4],queue:[3,4,22],quick:5,quickli:[3,10],quit:[2,3,4,10,14,23,44],qulaz:41,rais:[2,10,15,21,29,36,41],raise_commit:[15,36],raise_for_statu:44,raise_rollback:[15,36],raiseerr:44,ram:43,randint:[10,12],random:[10,12],rang:[10,12,41],rather:[4,10,23],raw:[2,4,5,12,13,14,22,29,33,41],raw_conn:[26,27,28],raw_connect:[29,41],raw_pool:[26,27,28,29,41],raw_transact:[15,26,27,28,36],rdbm:[38,45],re:[2,3,4,7,8,10,12,23,29,41],reach:[36,38],reaction:4,read:[2,3,10,21,22,23,24,29],read_commit:3,read_default_fil:26,read_default_group:26,read_root:4,readi:8,readm:[8,41],realiti:4,realli:1,reason:[3,4,26,27],receiv:[3,10,26,27],recent:[2,29],recogn:[3,15,23,41],recommend:[2,21,29,38],reconsid:4,record:[3,41],recov:41,recurs:[12,29],recv:1,redirect:24,reduc:[12,14],refactor:41,refer:[2,10,12,15,21,29],referenc:[5,29,36],reflect:24,refresh:41,regardless:3,regular:[3,12,14],reinvent:4,rel:6,relat:[2,10,12,43],relationship:[4,5,23,29,41],releas:[2,10,17,26,27,28,29,38,39],relev:[21,29,41],reli:4,reliabl:[3,4],reload:[23,32,44],remain:[10,41],rememb:[4,8,23],remind:4,remov:[2,23,41],renam:41,replac:[2,3,24,33,39,41],repli:4,repo:8,report:8,repr:[26,27,28,29],repres:[10,21,22,29,36],represent:41,req:8,requeijo:41,request:[4,5,10,23,38,39,44],requir:[2,3,4,8,14,21,23,24,29,41,44],requirements_dev:8,reserv:3,reset:[3,41],reset_loc:41,reskov:41,resourc:[2,4],respond:4,respons:[12,29,38,39],rest:[11,23,36,38],restor:3,restrict:21,result:[2,3,5,12,21,22,24,26,27,29,33,41],result_processor:[2,26,27],resultproxi:26,resum:[4,21],retriev:[2,12,23,44],retry_interv:44,retry_limit:44,return_model:[2,21,23,28,29],reus:[0,4,12,15,29,33,38,41],reusabl:[0,29],revamp:41,reveal:3,revers:[2,14,29],revert:[2,41],review:[3,41],revis:[5,11,44],rewritten:[14,41],rewrot:41,rez:43,ricardo:43,rich:[12,41],right:[0,3],risk:[2,3,14],riski:3,rm:8,ro:8,roald:41,role:8,roll:[3,15,29,36],rollback:[3,4,15,26,27,28,29,36,41],roman:41,room:45,root:[4,29],rootdir:44,rout:38,router:44,row:[2,3,7,10,12,14,23,26,27,28,29,33,41],rowproxi:[2,14,33,41],rsa:8,rst:[8,44],rule:[2,14,15,29,33],run:[1,2,3,4,5,6,7,8,11,12,29,33,38,39,44],run_sync:3,run_until_complet:45,runtim:[7,44],rv:44,sa:[3,7,44],sa_conn:29,sacrific:4,safe:[14,29],safe_connect:3,sai:[3,4,38],said:4,same:[2,3,4,5,6,7,11,12,14,15,21,23,24,29,33,38,39,41],sampl:[3,6],sanic:[13,17,37,41,43],sanicframework:43,saniti:3,save:[2,29,32],savepoint:[15,36],scalabl:4,scalar:[2,3,4,7,10,14,21,23,28,29,41,45],scenario:[2,4,7,12,14],scene:[2,3,7],schema:[6,14,17,18,19,21,24,26,27,29,43],schema_ext:21,schema_for_object:29,schema_visitor:21,schemadropp:34,schemagener:34,schemaitem:[21,41],scope:[4,38],scott:3,sebasti:43,sec:1,second:[2,3,4,12,23,29],secret:44,section:8,secur:10,see:[1,2,3,6,7,8,10,14,15,19,29],seek:4,seem:[1,14],seen:[3,7],select:[2,3,4,7,10,11,12,14,15,21,23,26,29,33,41,43,45],select_from:[10,12,33],self:[5,10,11,21,23,26,27,29,33,38],send:[3,4,8,27],sens:4,sent:21,sep:12,separ:[4,8,12,15,41],sequenc:[2,26,27,34],sequence_nam:27,sergei:41,seri:[2,29],serializ:3,serv:4,server:[2,4,8,10,29,38,39,41,44],server_default:[10,11,12],server_public_kei:26,server_set:27,servic:43,session:[3,4,44],sessionmak:4,set:[2,3,5,7,8,10,12,14,21,22,23,24,26,27,29,33,38,39,41,45],set_bind:[2,7,10,21,22,29,41,45],set_except:10,set_isol:26,set_isolation_level:[3,26,27],set_main_opt:44,set_result:10,setattr:[12,33,41],setter:[3,10,12,21],settl:38,setup:[6,27,39],sever:[2,21,23,29],shadow:22,shall:[15,23,38],shallow:3,share:[2,7,29,38,39,41],shortcut:[2,4,12,15,19,21,22,23,29,33,41],shortest:4,should:[2,3,4,8,12,13,21,23,24,26,27,29,31,39,41],shouldn:[4,41],show:[3,12],shown:[2,36],shtrikker:41,shut:44,shutdown:44,side:[2,4,12,29],sign:1,signific:3,silenc:1,simeon:41,similar:[2,3,6,7,8,12,14,15,21,23,41],similarli:[2,12,14,15,23,29,41],simpl:[2,4,6,10,14,21,41,43],simpler:2,simplest:33,simpli:[2,3,4,10,11,12,21,23,29,33,38,39],simplif:3,simplifi:7,simul:[2,3,10],sinc:[1,41],singl:[2,3,4,12,14,23,26,27,29],situat:[4,29,36],skip:[15,36],sleep:[1,2,4],slower:4,small:2,smaller:4,smarter:4,so:[2,3,4,7,8,10,12,14,15,21,23,24,26,27,29,31,36,41],soft:3,softwar:[3,43],sole:[26,27],solut:[0,4,31],solv:[3,4],some:[2,3,4,6,7,8,10,11,12,14,23,29,39,41],someth:[2,4,12],sometim:[2,3,4,38],soon:38,sourc:[23,29,41,43],speak:[2,3],special:[12,26,27,38],specif:[2,23],specifi:[2,11,12,14,15,23,24,26,27,29,33,39,41],sql:[2,3,4,5,11,12,14,21,22,23,26,27,29,34,41,43,45],sql_mode:26,sqlalchemi:[0,2,4,5,6,7,11,12,14,16,19,21,22,23,24,26,27,29,33,34,35,39,41,43,44,45],sqltype:[26,27],src:[41,43,44],ssl:[5,8,26,27,39,41,44],ssl_cert_fil:8,ssl_key_fil:8,stabil:10,stabl:[3,41,43],stack:[2,29,41],standard:[2,3,4],starleet:41,starlett:[10,17,31,37,41,43,44],start:[2,3,4,5,7,10,15,23,29,36,38,41,44],startup:44,starv:0,starvat:4,state:4,stateless:[4,10],statement:[2,3,4,5,10,14,15,19,22,23,26,27,28,41],statement_cache_s:27,statement_compil:[26,27],statu:[2,3,7,14,15,21,23,28,29,41,45],status_cod:44,stave:4,step:[2,3,4,7],stereotyp:4,still:[2,3,4,10,12,14,22,24,29,33,36,41],stop:[4,15,38],storag:[24,41],store:[1,2,3,11,12,24,26],stori:3,storm:[3,41],str:[4,11,22,33,44],straightforward:4,strategi:[2,17,18,19,33],string:[2,7,10,11,12,14,21,22,23,24,26,27,29,38,41,43,45],stringproperti:[11,32],strongli:4,structur:[3,6,12,23,29],stub:43,stuff:3,style:[4,12,14,41],sub:[4,10,24,33,41],subclass:[2,14,21,24,33,36,41],subj:8,subload:12,submit:8,submodul:[17,18],subpackag:[17,18],subqueri:[23,41],subsequ:3,subset:8,succeed:29,success:[3,6],successfulli:[3,36],such:[2,3,4,10,22,23,26,27,29],suffici:3,suffix:7,sugar:4,suit:[23,41],summari:4,support:[2,3,5,8,11,12,17,20,21,23,26,29,33,37,41,43,44],support_prepar:[26,28],support_return:[26,28],supports_native_decim:[26,27],suppos:[2,7,24,33,36],sure:[3,4,8,12,29,38],surpris:3,svx:8,swagger:44,sweet:1,symbol:21,sync:[3,10],sync_engin:3,sync_safe_connect:3,synchron:21,syntax:8,sys:31,system:[2,10,12,44],t1:3,tabl:[5,6,7,12,14,15,21,23,24,26,27,34,41,44],table_nam:[24,26,27],tableclaus:14,take:[2,4,7,10,13,14,23,29,33],taken:[2,29,38],talk:4,target_metadata:[6,10,44],task:[2,4,17,29,39],tast:14,team:[23,41],team_id:23,technic:23,telegram:43,tell:[2,3],temporarili:3,ten:4,termin:8,test:[3,8,41,44],test_aiohttp:8,test_crud:44,test_gino:8,test_us:44,testclient:44,text:[3,8,10,11,12,14,21,33,41],textual:[2,12],than:[2,3,4,7,10,12,13,15,23,24,29,43],thank:[2,41],that:[1,2,3,4,6,8,10,12,14,15,21,22,23,24,26,27,29,31,33,38,39,41],the:[0,1,2,4,5,6,8,11,12,13,14,15,19,21,22,23,24,26,27,29,31,33,36,38,39,41,43,44,45],thei:[2,3,4,7,8,10,12,14,15,23,24,29,41,45],their:[2,3,4,10,12,13,14,21,23,24],them:[3,4,6,7,10,11,12,23,41],themselv:7,then:[2,3,4,7,8,10,12,14,23,26,27,29,33,39,41],theoret:[4,41],there:[1,2,3,4,5,7,12,13,14,15,22,23,29,36,41],therefor:[2,4,7,10,14,21,23,29,38],these:[2,4,6,7,8,21,23,24,26,29,41],thi:[2,3,4,6,7,8,10,11,12,14,15,20,21,22,23,24,26,27,29,31,33,35,36,38,39,40,41,45],thing:[2,3,4,6,7,14,24,41],think:[3,4,10],third:[12,14],those:[4,29],though:[3,4,14,23,38],thought:3,thousand:4,thread:[1,2,3,4],three:2,through:[2,5,8,12,15,21,31,41],throughput:4,thu:[2,4,14,15,29,41],tiago:41,tiangolo:43,tiger:3,tim:43,time:[1,2,3,4,6,10,11,12,15,19,21,23,26,29,38],timeout:[7,21,23,26,27,28,29,41],timeouterror:29,timestamp:11,timezon:24,tini:4,tip:5,titl:[43,44],to:[0,1,2,3,5,6,8,11,13,14,15,17,19,20,21,22,23,24,26,27,29,31,33,36,38,39,43,44,45],to_dict:[23,41,44],togeth:[2,3,29,36],token:4,toml:44,toni:[41,43],too:[2,3,4,10,14,15,29],took:3,tool:[4,6,43,44],toolkit:6,top:[2,3,4,10,14,29],tornado:[17,37,41,43],tortois:43,total:4,touch:[15,36],touchabl:2,tox:8,trackedmixin:24,tradit:[10,14],transact:[2,3,4,5,10,12,17,18,19,21,26,27,28,29,38,43,44],transaction_isol:3,transform:12,transit:3,translat:[2,11,12],trap:[15,36],travers:2,traverse_singl:34,treat:[2,3,7,12,24,29,35],tree:12,tri:[4,29,41],trigger:[4,24],trio:10,ts:33,tupl:[2,10,12,23,29,33],tupleload:[10,12,33,41],turn:[1,2,3,12,29,38,41],tutori:[14,43,44],twice:[2,5,26,27],twist:[4,43],two:[2,4,7,10,12,14,15,23,29,36],tx1:[15,36],tx2:[15,36],tx3:36,tx:[15,27,29,36,41],txt:8,type:[2,5,10,11,12,14,23,24,26,29,33,36,41],type_nam:27,typic:[4,10,26],ua1:12,ua2:12,ubuntu:43,uc:43,ui:44,uid:[7,12,44],ultim:4,ultra:43,um:1,unbind:21,under:[2,3,10,12,14,21,23,24,33,41,44],underli:[2,3,14,15,29,36,41],unfortun:[3,4],unicod:[6,10,12,14,24,26,27,38,44,45],unifi:[2,41],uninitializederror:[10,30,41],uniqu:[2,12,45],unique_constraint:24,unique_id:24,uniqueconstraint:24,unix:43,unix_socket:26,unknown:[12,41],unknownjsonpropertyerror:30,unless:[2,4,23],unnam:[41,44],unnecessarili:38,unpin:41,unpredict:[4,38],unrecogn:39,unreli:4,unreus:2,unset:2,unspecifi:23,untest:2,until:[10,11,29],unus:[2,4],unwant:14,unwrap:[26,27],up:[1,2,5,8,10,12,21,26,27,29,36,39],updat:[2,3,5,8,11,23,24,29,33,36,41,43,44,45],update_execution_opt:[29,41],updaterequest:[23,45],upgrad:[2,3,6,10,41,44],upon:[3,15,26,27,36],upstream:[3,10],urh:1,url:[2,6,21,26,27,28,35,39,41,44,45],us:[3,4],usabl:[2,29,41],usag:[2,5,6,23,29,39,41],use:[2,3,4,5,6,11,12,13,14,15,21,22,23,26,27,29,31,33,36,38,39,41,43,44],use_connection_for_request:[39,44],use_unicod:26,used:[2,3,7,10,12,13,14,21,22,23,24,26,27,29,33,41],useful:[2,4,7,14,23,29,33,36],user1:23,user2:23,user:[2,3,4,5,6,7,8,11,12,13,14,21,23,24,26,27,29,33,36,38,39,43,44,45],user_gett:7,user_id:[10,12,14,23,38],user_queri:7,user_t:7,usermodel:44,usernam:[6,44],users_t:2,uses:[3,10,12,23,35],using:[2,3,4,7,8,10,11,12,14,15,23,24,26,27,29,33,38,41],usual:[2,3,4,7,10,11,12,14,21,23,24,29],utc:12,util:[3,24,31],uuid4:44,uuid:44,uvicorn:[43,44],uvicornwork:44,uvloop:[1,43],v7oze:29,val:[10,11,32],valid:[3,14],valu:[2,3,7,10,12,14,15,21,23,24,26,27,29,32,33,38,41,45],valueload:33,van:41,vanilla:[2,14],vargovcik:41,variabl:[6,43],variant:2,ve:[1,3,4,12],venv:44,veri:[1,2,3,4,11,12,14,23,26,27,29,36,38],verifi:3,version:[3,6,8,10,19,20,39,41,44],view:[26,44],virtual:[3,8],virtualenv:44,virtualenvwrapp:8,visit:[10,12,21],visit_foreign_key_constraint:34,visit_index:34,visit_metadata:34,visit_sequ:34,visit_t:34,visual:43,vladimir:41,volkova:41,volunt:8,wai:[2,3,4,8,10,11,12,14,15,21,29],wait:[3,4,11,15,23,29,44],wang:[41,43],wanna:[1,4],want:[2,3,4,5,6,8,14,21,23,29,38],ware:10,warehous:22,warn:[41,44],was:[2,3,4,12,15,26,29,36],wast:[2,4],watch:[1,43],we:[2,3,4,6,7,10,11,12,14,15,21,38,41],weakref:41,web:[7,10,41,43],websit:8,weird:3,welcom:[8,14,41,43],well:[1,2,3,4,24],were:4,what:[1,2,3,4,5,14,15,21],whatev:[2,4,12,23,29],wheel:4,when:[0,1,2,3,6,7,8,10,12,14,15,19,20,21,22,23,24,26,29,33,36,38,41],where:[2,3,4,7,10,11,12,14,21,23,29,33,36,45],whether:[29,36,41],which:[2,3,4,7,10,12,14,21,22,23,24,26,27,29,36,38,41],whichev:41,whole:41,whose:[3,14,36],why:[4,12],wide:[3,26,27],wiki:[1,43],wikipedia:[1,43],will:[2,3,4,5,6,7,8,11,12,15,21,22,23,24,26,27,29,31,33,35,36,39,41,44,45],wip:[38,40],wire:3,wise:[3,4],wish:[2,8,12,29],with_bind:[2,7,10,12,14,21,22,41],with_tabl:[7,24,41],within:[3,4,15,26,27,29,38,41],without:[2,3,4,7,11,14,15,21,29,41],won:[1,2,3,4,7,10,14,15,22,36,41],wonder:1,word:3,work:[2,3,4,5,6,8,12,14,17,23,24,26,29,37,41],workaround:[3,10],workdir:44,worker:44,world:[3,14,38],worri:[2,4,10,15,41],worth:[2,14],would:[3,4,10,12,21,23,33],wrap:[3,10,24],wrapper:[2,3,4,10,24,29,43],write:[2,4,10,12],written:[14,43],wrong:[4,41],wrote:4,ww4ronfhiqi:43,www:43,wysiwyg:3,x509:8,xss:44,xxx:[23,43],xxxx:5,yai:[1,10],yeah:3,year:3,yes:[2,3],yet:[1,10,11,29],yield:[4,14,23,24,29,33,44],yml:44,you:[1,2,3,4,6,7,8,10,11,12,14,15,21,22,23,24,29,36,38,39,41],your:[1,2,3,4,6,8,10,12,14,15,21,29,41,44],your_name_her:8,yourdbnam:44,youtub:43,yurii:41,za:41,zen:43,zenof:43,zero:[23,29],zh:43,zone:[11,12]},titles:["\u539f\u7406\u8bf4\u660e","\u5f02\u6b65\u7f16\u7a0b\u57fa\u7840","\u5f15\u64ce\u4e0e\u8fde\u63a5","SQLAlchemy 2.0","\u4e3a\u4ec0\u4e48\u8981\u7528\u5f02\u6b65 ORM\uff1f","\u8fdb\u9636\u7528\u6cd5","\u4f7f\u7528 Alembic","\u9884\u5236\u67e5\u8be2","\u8d21\u732e","\u589e\u5220\u6539\u67e5","\u5e38\u89c1\u95ee\u9898","JSON \u6269\u5c55\u5c5e\u6027","\u52a0\u8f7d\u5668\u4e0e\u5173\u7cfb","\u8fde\u63a5\u6c60","\u8868\u7ed3\u6784\u5b9a\u4e49","\u6570\u636e\u5e93\u4e8b\u52a1","\u6b22\u8fce\u6765\u5230 GINO \u7684\u6587\u6863\uff01","\u53c2\u8003\u624b\u518c","API \u53c2\u8003","gino package","gino.aiocontextvars module","gino.api module","gino.bakery module","gino.crud module","gino.declarative module","gino.dialects package","gino.dialects.aiomysql module","gino.dialects.asyncpg module","gino.dialects.base module","gino.engine module","gino.exceptions module","gino.ext package","gino.json_support module","gino.loader module","gino.schema module","gino.strategies module","gino.transaction module","\u6269\u5c55","Sanic Support","Starlette Support","Tornado Support","\u7248\u672c\u5386\u53f2","\u4e0a\u624b\u6559\u7a0b","\u5b98\u5ba3\uff1aPython \u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668","\u642d\u5efa\u4e00\u4e2a FastAPI \u670d\u52a1\u5668","GINO \u57fa\u7840\u6559\u7a0b"],titleterms:{"02":41,"03":41,"04":41,"05":41,"06":41,"07":41,"08":41,"09":41,"10":41,"11":41,"12":41,"14":41,"15":41,"16":41,"18":41,"19":41,"20":41,"2017":41,"2018":41,"2019":41,"2020":41,"21":41,"23":41,"24":41,"25":41,"26":41,"28":41,"do":10,"for":[3,10],"in":10,"with":[7,10,38,39],access:4,admin:10,advanc:12,aiocontextvar:[10,20],aiomysql:26,alemb:[6,10,44],and:10,api:[3,7,18,21,41,44],appli:6,are:7,async:3,asynchron:4,asyncio:[4,10],asyncpg:27,auto:3,autocommit:3,avail:7,bakedqueri:7,bakeri:[7,22],bare:7,base:28,basic:15,batch:10,be:[4,10],bulk:10,can:10,column:10,commit:3,complex:10,complic:3,configur:39,connect:[2,10,39],content:[19,25,31],contextvar:41,contribut:8,control:15,core:14,creat:[2,6,11],crud:23,current_connect:2,custom:7,databas:[4,10],db:[3,6],declar:24,defin:10,develop:41,dialect:[25,26,27,28],differ:10,django:10,doe:10,don:[4,7],done:4,earli:41,engin:[2,7,10,14,29,41],except:30,execut:[2,10],exist:10,explicit:4,express:12,ext:31,fastapi:44,featur:10,first:6,fly:10,get:8,gino:[7,10,14,16,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,41,43,44,45],ginoconnect:41,guidelin:8,help:4,hook:11,how:[4,7,10],implicit:2,index:[10,11],initi:10,insert:10,integr:7,interfac:10,is:10,isol:3,it:10,join:10,json:11,json_support:32,lazi:[2,39],level:3,load:10,loader:[7,12,33],local:41,manag:2,mani:12,manual:15,migrat:[6,41],model:[12,44],modul:[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],multipl:10,nest:15,none_as_non:41,not:10,of:[8,10,12],on:[6,7,10,11],one:12,or:10,orm:[4,10,14],other:12,packag:[19,25,31],paramet:10,pend:41,prepar:7,print:10,product:4,properti:11,pull:8,python:43,queri:[2,10,41],quick:11,raw:10,referenc:12,relationship:[10,12],releas:41,request:8,result:10,reus:2,reusabl:2,revis:6,right:4,run:10,same:10,sanic:38,schema:34,self:12,set:6,solut:3,sql:10,sqlalchemi:[3,10],ssl:10,starlett:39,start:[8,11],starv:4,statement:7,strategi:35,submodul:[19,25],subpackag:19,support:[10,38,39,40],tabl:10,task:41,the:[3,7,10],there:10,through:10,tip:8,to:[4,7,10,12,41],tornado:40,transact:[15,36,41],twice:10,type:8,up:6,updat:10,usag:[12,15],use:[7,10],user:10,want:7,what:[7,10],when:4,will:10,work:[10,38,39],xxxx:10}}) \ No newline at end of file diff --git a/docs/zh/1.1b2/tutorials.html b/docs/zh/1.1b2/tutorials.html new file mode 100644 index 0000000..93be244 --- /dev/null +++ b/docs/zh/1.1b2/tutorials.html @@ -0,0 +1,235 @@ + + + + + + + + 上手教程 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/tutorials/announcement.html b/docs/zh/1.1b2/tutorials/announcement.html new file mode 100644 index 0000000..080b2c4 --- /dev/null +++ b/docs/zh/1.1b2/tutorials/announcement.html @@ -0,0 +1,491 @@ + + + + + + + + 官宣:Python 异步编程再添一利器 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    官宣:Python 异步编程再添一利器

    +
    +

    GINO 填补了国内外 asyncio ORM 领域的空白

    +
    +

    随着 Tornado1 和 asyncio2 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +GINO 了解一下!

    +../_images/python-gino.webp +
    +

    GINO 是谁

    +

    GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义3手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,你肯定要问一句“这是谁”。

    +

    ORM,即关系对象映射(Object-Relational Mapping4),是一类开发人员喜闻乐见的效率工具,它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢?

    +

    因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 current_user.name +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试?

    +

    传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,但应用到大型项目中却十分考验开发人员的平均水平。

    +

    所有这些问题如果再放进异步编程的环境里,那就是 O(n2) 的复杂度了——哦不,是 +O(2n)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。

    +

    所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,我索性在 1.0 稳定版发布的前夕做个年终总结。

    +
    +
    +

    先说“方便快捷”

    +
    +

    “方便快捷”主要说的是开发效率。

    +
    +

    重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。

    +
    from gino import Gino
    +db = Gino()
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +
    +

    这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟?

    +

    没有错,这就是 SQLAlchemy ORM5 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core6(SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic7、各种 SQLAlchemy 的增强插件8、专业领域的 PostGIS/geoalchemy9等,GINO 全都兼容。

    +

    是不是十分方便、十分快捷?不止这样。

    +

    GINO 一站式地解决了常用 CRUD 快捷方式10、上下文管理(aiocontextvars1112)、 +数据库事务封装和嵌套13、连接池管理和懒加载14等多项便捷功能,无额外依赖关系,即装即用。

    +
    daisy = await User.create(name="daisy")
    +await daisy.update(name="Daisy").apply()
    +
    +
    +

    GINO 还提供了15各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado1、aiohttp16、Sanic17、FastAPI18/Starlette19、Quart20什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。

    +

    为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa21 的降维打击:

    +
      +
    1. 最少侵入型22:SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。

    2. +
    3. 终身不婚型23:天生厌恶“对象”,只愿定义“表”,空手接 SQL。

    4. +
    5. 火力全开型24:最大程度的便利,非典型异步 ORM。

    6. +
    +

    最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、一秒可读百万行的 asyncpg25,以及 uvloop26(可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,深受俄罗斯和乌克兰人民的爱戴。

    +
    +
    +

    再说“简单明了”

    +
    +

    Explicit is better than implicit.

    +

    Simple is better than complex.

    +

    –The Zen of Python, PEP 2027

    +
    +

    Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,因此 GINO 的很多设计都受到了明确性的影响。

    +

    比如说,GINO 的 Model 是完全无状态的普通 Python 对象(POPO28—— 例如前面的 User +类,它的实例 daisy 就是内存里面的常规对象,你可以用 daisy.name 访问属性,也可以用 +daisy.name = "DAISY" 来修改属性,或者用 u = User() 来创建新的实例,这些操作都不会访问数据库,绝对绿色环保无毒副作用。

    +

    等到需要操作数据库的时候,你一定会有感知的。比如执行 INSERT 要用 +u = await User.create(),而 UPDATE 则是 +await u.update(name="Daisy").apply()

    +
    +

    提示

    +

    其中,u.update(name="Daisy") 与 u.name = "Daisy" 类似,都是只在内存里修改对象的属性,不同的是 u.update() 还会返回一个包含本次变更的中间结果,对其执行 await xxx.apply() 则会将这些变更应用到数据库里。

    +
    +

    这里的 await 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 await +就没有数据库操作。

    +

    另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders29。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 u = await User.get(1) 可以直接获取到 ID 为 1 的用户对象。但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。加载器的用法也是很简单的,比如一个用户可能写了很多本书:

    +
    class Book(db.Model):
    +    __tablename__ = "books"
    +    id = db.Column(db.Integer, primary_key=True)
    +    title = db.Column(db.String)
    +    author_id = db.ForeignKey("users.id")
    +
    +
    +

    然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者:

    +
    query = Book.outerjoin(User).select()
    +loader = Book.load(author=User)
    +async for book in query.gino.load(loader).iterate():
    +    print(book.title, "written by", book.author.name)
    +
    +
    +

    很简单的一个外连接查询 Book.outerjoin(User),配合一个直观的加载器 +Book.load(author=User),就实现了:

    +
      +
    1. 执行 SELECT * FROM books LEFT JOIN users ON ...

    2. +
    3. 将数据库返回结果的每一行中,属于 books 的字段加载成一个 Book 实例;

    4. +
    5. 然后将该行中剩下的属于 users 的字段加载成一个 User 实例;

    6. +
    7. 最后将 User 实例设置到 Book 实例的 author 属性上。

    8. +
    +

    既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。

    +
    +
    +

    优势与不足

    +

    随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM30、ORM31(是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。

    +

    GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,可以谨慎地用于生产环境了。

    +

    以下是近来统计到的关于 GINO 的应用案例:

    + +

    另外,GINO 还贴心地提供了中文文档32,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!):

    +../_images/docs.webp +

    GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。

    +
    +
    +

    建设社会主义

    +

    GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 4888 元的 PyCharm +专业版全家桶 License 一枚33(没有发票)。

    +

    目前急需帮助的有:

    +
      +
    1. 各个 Web 框架插件的维护工作需要多人认领;

    2. +
    3. 更多的例子和文档,以及中文、俄文的翻译;

    4. +
    5. MySQL 的支持。

    6. +
    +

    以及下面这些一直需要的帮助:

    +
      +
    1. 用 GINO,找 bug,提建议;

    2. +
    3. 修 bug,做功能,提 PR;

    4. +
    5. 维护社区,回答问题,参与讨论;

    6. +
    7. 最后也是最重要的:去 GitHub 上给 GINO 加一颗星星!

    8. +
    +../_images/happy-hacking.png +
    +
    +

    关于作者

    +

    87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P

    +

    特别感谢 Tony Wang 对 GINO 项目的贡献,以及所有人的贡献。

    +
    +
    +

    参考文献

    +
    +
    1(1,2)
    +

    Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado.

    +
    +
    2
    +

    Python Software Foundation. asyncio — 异步 I/O. +https://docs.python.org/zh-cn/3/library/asyncio.html.

    +
    +
    3
    +

    维基百科. GNU. https://zh.wikipedia.org/wiki/GNU.

    +
    +
    4
    +

    维基百科. 对象关系映射. +https://zh.wikipedia.org/wiki/对象关系映射.

    +
    +
    5
    +

    维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy.

    +
    +
    6
    +

    Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. +https://docs.sqlalchemy.org/en/13/core/index.html.

    +
    +
    7
    +

    Michael Bayer. Welcome to Alembic’s documentation!. +https://alembic.sqlalchemy.org/en/latest/.

    +
    +
    8
    +

    Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. +https://github.com/dahlia/awesome-sqlalchemy.

    +
    +
    9
    +

    Ricardo GarciaSilva. Does it have postgis support?. +https://github.com/python-gino/gino/issues/627.

    +
    +
    10
    +

    Fantix King. GINO 基础教程 - 增删改查. +https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations.

    +
    +
    11
    +

    Python Software Foundation. contextvars —Context Variables. +https://docs.python.org/3/library/contextvars.html.

    +
    +
    12
    +

    Fantix King. gino/aiocontextvars.py. +https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py.

    +
    +
    13
    +

    Fantix King. 数据库事务. +https://python-gino.org/docs/zh/master/how-to/transaction.html.

    +
    +
    14
    +

    Fantix King. 引擎与连接. +https://python-gino.org/docs/zh/master/explanation/engine.html.

    +
    +
    15
    +

    GINO Community. GINO Community. https://github.com/python-gino/.

    +
    +
    16
    +

    aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/.

    +
    +
    17
    +

    Sanic Community. SanicFramework. https://sanicframework.org/.

    +
    +
    18
    +

    Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/.

    +
    +
    19
    +

    Encode OSS. Starlette. https://www.starlette.io/.

    +
    +
    20
    +

    Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/.

    +
    +
    21
    +

    Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. +https://github.com/CanopyTax/asyncpgsa.

    +
    +
    22
    +

    Fantix King. 表结构定义 -GINO 引擎. +https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine.

    +
    +
    23
    +

    Fantix King. 表结构定义 - GINO core. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core.

    +
    +
    24
    +

    Fantix King. 表结构定义 - GINO ORM. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm.

    +
    +
    25
    +

    MagicStack Inc. +asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. +https://github.com/MagicStack/asyncpg.

    +
    +
    26
    +

    MagicStack Inc. uvloop -Ultra fast asyncio event loop. +https://github.com/MagicStack/uvloop.

    +
    +
    27
    +

    Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/.

    +
    +
    28
    +

    Wikipedia. Plain old Java object. +https://en.wikipedia.org/wiki/Plain_old_Java_object.

    +
    +
    29
    +

    Fantix King. 加载器与关系. +https://python-gino.org/docs/zh/master/how-to/loaders.html.

    +
    +
    30
    +

    Andrey Bondar. Tortoise ORM. https://tortoise.github.io/.

    +
    +
    31
    +

    Encode OSS. ORM - An async ORM. https://github.com/encode/orm.

    +
    +
    32
    +

    Fantix King. 欢迎来到 GINO 的文档!. +https://python-gino.org/docs/zh/master/index.html.

    +
    +
    33
    +

    JetBrains s.r.o. Open Source Licenses. +https://www.jetbrains.com/community/opensource/#support.

    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/tutorials/fastapi.html b/docs/zh/1.1b2/tutorials/fastapi.html new file mode 100644 index 0000000..daaaedc --- /dev/null +++ b/docs/zh/1.1b2/tutorials/fastapi.html @@ -0,0 +1,668 @@ + + + + + + + + 搭建一个 FastAPI 服务器 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    搭建一个 FastAPI 服务器

    +

    在这篇教程里,我们会一起搭建一个用于生产环境的 FastAPI 服务器。完整的示例代码在这里

    +

    写好之后,整个应用技术栈会是这样的:

    +../_images/gino-fastapi.svg
    +

    创建一个新项目

    +

    这里我们尝试用亮瞎眼的 Poetry 来管理我们的项目,而不是传统的 pip。请跟随链接安装 Poetry,并且在一个空文件夹中创建我们的新项目:

    +
    $ mkdir gino-fastapi-demo
    +$ cd gino-fastapi-demo
    +$ git init
    +$ poetry init
    +
    +
    +

    然后跟着 Poetry 的向导完成初始化——关于交互式创建依赖的两个问题,您可以回答“no”,因为我们会在下面手动创建。其他问题都可以用默认值,只是一定保证包的名字是 gino-fastapi-demo

    +
    +
    +

    添加依赖关系

    +

    FastAPI 底层用的是 Starlette 框架,所以我们就可以直接使用 GINO 的 Starlette 扩展。执行以下命令即可:

    +
    $ poetry add gino[starlette]
    +
    +
    +

    接着我们添加 FastAPI,以及快成一道闪电的 ASGI 服务器 Uvicorn,还有用作生产环境的应用服务器 Gunicorn

    +
    $ poetry add fastapi uvicorn gunicorn
    +
    +
    +

    我们将用 Alembic 来管理数据库表结构变更。因为 Alembic 只兼容传统的 DB-API 驱动,所以我们还得加上 psycopg

    +
    $ poetry add alembic psycopg2
    +
    +
    +

    最后,测试框架选用 pytest,我们将其添加到开发环境的依赖关系中。同时也加上 requests 库,因为 StarletteTestClient 要用到它:

    +
    $ poetry add -D pytest requests
    +
    +
    +
    +

    提示

    +

    经过了上面的步骤,Poetry 会悄没声地帮我们自动创建一个 virtualenv,并且把所有的依赖关系装到这个虚拟环境里。在本教程后面的步骤里,我们会假定继续使用这个环境。但是,您也可以创建自己的 virtualenv,只要激活了 Poetry 就会用它。

    +
    +

    以上。下面是 Poetry 给我创建出来的 pyproject.toml 文件内容,您的应该也长得差不多:

    +
    [tool.poetry]
    +name = "gino-fastapi-demo"
    +version = "0.1.0"
    +description = ""
    +authors = ["Fantix King <fantix.king@gmail.com>"]
    +
    +[tool.poetry.dependencies]
    +python = "^3.8"
    +gino = {version = "^1.0", extras = ["starlette"]}
    +fastapi = "^0.54.1"
    +uvicorn = "^0.11.3"
    +gunicorn = "^20.0.4"
    +alembic = "^1.4.2"
    +psycopg2 = "^2.8.5"
    +
    +[tool.poetry.dev-dependencies]
    +pytest = "^5.4.1"
    +requests = "^2.23.0"
    +
    +[build-system]
    +requires = ["poetry>=0.12"]
    +build-backend = "poetry.masonry.api"
    +
    +
    +../_images/gino-fastapi-poetry.svg

    同时自动生成的还有一个叫 poetry.lock 的文件,内容是当前完整依赖关系树的精确版本号,当前的目录结构如右图所示。现在让我们把这两个文件加到 Git 仓库中(以后的步骤就不再演示 Git 的操作了):

    +
    $ git add pyproject.toml poetry.lock
    +$ git commit -m 'add project dependencies'
    +
    +
    +
    +
    +

    编写一个简单的服务器

    +

    现在让我们写一点 Python 的代码吧。

    +

    我们要创建一个 src 文件夹,用来装所有的 Python 文件,如下图所示。这种目录结构叫做“src 布局”,能让项目结构更清晰。

    +../_images/gino-fastapi-src.svg

    我们项目的顶层 Python 包叫做 gino_fastapi_demo,我们在里面创建两个 Python 模块:

    +
      +
    • asgi 作为 ASGI 的入口,将被 ASGI 服务器直接使用

    • +
    • main 用来初始化我们自己的服务器

    • +
    +

    下面是 main.py 的内容:

    +
    from fastapi import FastAPI
    +
    +def get_app():
    +    app = FastAPI(title="GINO FastAPI Demo")
    +    return app
    +
    +
    +

    asgi.py 里,我们只需要实例化我们的应用即可:

    +
    from .main import get_app
    +
    +app = get_app()
    +
    +
    +

    然后执行 poetry install 来把我们的 Python 包以开发模式链接到 PYTHONPATH 中,接下来就可以启动 Uvicorn 的开发服务器了:

    +
    $ poetry install
    +Installing dependencies from lock file
    +
    +No dependencies to install or update
    +
    +  - Installing gino-fastapi-demo (0.1.0)
    +
    +$ poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    +INFO:     Started reloader process [53010]
    +INFO:     Started server process [53015]
    +INFO:     Waiting for application startup.
    +INFO:     Application startup complete.
    +
    +
    +

    这里的 --reload 选项会启用 Uvicorn 的自动加载功能,当我们的 Python 代码发生变动的时候,Uvicorn 会自动加载使用新代码。现在可以访问 http://127.0.0.1:8000/docs 了,试一下我们新 FastAPI 服务器的 Swagger UI 接口文档。

    +
    +

    提示

    +

    正如之前提到的,如果您使用自己的虚拟环境,那么此处的 poetry run uvicorn 就可以简化为 uvicorn

    +

    poetry run 是一个快捷命令,用于在 Poetry 管理的虚拟环境中执行后续的命令。

    +
    +
    +
    +

    添加 GINO 扩展

    +../_images/gino-fastapi-config.svg

    现在让我们把 GINO 添加到服务器里。

    +

    首先,我们需要有办法来配置数据库。在本教程中,我们选用 Starlette配置系统。创建文件 src/gino_fastapi_demo/config.py,内容为:

    +
    from sqlalchemy.engine.url import URL, make_url
    +from starlette.config import Config
    +from starlette.datastructures import Secret
    +
    +config = Config(".env")
    +
    +DB_DRIVER = config("DB_DRIVER", default="postgresql")
    +DB_HOST = config("DB_HOST", default=None)
    +DB_PORT = config("DB_PORT", cast=int, default=None)
    +DB_USER = config("DB_USER", default=None)
    +DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    +DB_DATABASE = config("DB_DATABASE", default=None)
    +DB_DSN = config(
    +    "DB_DSN",
    +    cast=make_url,
    +    default=URL(
    +        drivername=DB_DRIVER,
    +        username=DB_USER,
    +        password=DB_PASSWORD,
    +        host=DB_HOST,
    +        port=DB_PORT,
    +        database=DB_DATABASE,
    +    ),
    +)
    +DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1)
    +DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16)
    +DB_ECHO = config("DB_ECHO", cast=bool, default=False)
    +DB_SSL = config("DB_SSL", default=None)
    +DB_USE_CONNECTION_FOR_REQUEST = config(
    +    "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True
    +)
    +DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1)
    +DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1)
    +
    +
    +

    这个配置文件会首先从环境变量中加载配置参数,如果没找到,则会从当前路径(通常是项目顶层目录)下一个叫 .env 的文件中加载,最后不行才会使用上面定义的默认值。比如,您即可以在命令行中设置:

    +
    $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +
    +
    +../_images/gino-fastapi-env.svg

    也可以在 .env 文件中设置(一定不要将该文件提交到 Git 中,记得在 .gitignore 里加上它):

    +
    DB_HOST=localhost
    +DB_USER=postgres
    +
    +
    +

    接下来就该创建 PostgreSQL 数据库实例并且将连接参数设置好了。创建数据库实例的命令通常是 createdb yourdbname,但不同平台可能有不同的方式,此教程里就不具体写了。

    +
    +

    小技巧

    +

    另外,您也可以使用 DB_DSN 来定义数据库连接参数,比如 postgresql://user:password@localhost:5432/dbname,它会覆盖出现在它前面的单个的配置,比如 DB_HOST

    +

    除了默认值不算之外,只要您定义了 DB_DSN ——不管是在环境变量中还是在 .env 文件中,它都比单个的连接参数有更高的优先级。比如哪怕环境变量中定义了 DB_HOST.env 文件中的 DB_DSN 仍然能够覆盖前者的值。

    +
    +../_images/gino-fastapi-models.svg

    然后创建一个 Python 的二级包 gino_fastapi_demo.models,用来封装数据库相关的代码。将下面的代码添加到 src/gino_fastapi_demo/models/__init__.py

    +
    from gino.ext.starlette import Gino
    +
    +from .. import config
    +
    +db = Gino(
    +    dsn=config.DB_DSN,
    +    pool_min_size=config.DB_POOL_MIN_SIZE,
    +    pool_max_size=config.DB_POOL_MAX_SIZE,
    +    echo=config.DB_ECHO,
    +    ssl=config.DB_SSL,
    +    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    +    retry_limit=config.DB_RETRY_LIMIT,
    +    retry_interval=config.DB_RETRY_INTERVAL,
    +)
    +
    +
    +

    最后,修改 src/gino_fastapi_demo/main.py,安装 GINO 扩展:

    +
     from fastapi import FastAPI
    ++
    ++from .models import db
    +
    + def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    ++    db.init_app(app)
    +     return app
    +
    +
    +

    保存该文件后,您应该可以看到 Uvicorn 服务器重载了我们的变更,然后连上了数据库:

    +
    WARNING:  Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading...
    +INFO:     Shutting down
    +INFO:     Waiting for application shutdown.
    +INFO:     Application shutdown complete.
    +INFO:     Finished server process [63562]
    +INFO:     Started server process [63563]
    +INFO:     Waiting for application startup.
    +INFO:     Connecting to the database: postgresql://fantix:***@localhost
    +INFO:     Database connection pool created: <asyncpg.pool.Pool max=16 min=1 cur=1 use=0>
    +INFO:     Application startup complete.
    +
    +
    +
    +
    +

    创建 model 及 API

    +../_images/gino-fastapi-models-users.svg

    现在轮到实现 API 逻辑了。比方说我们打算做一个用户管理的服务,可以添加、查看和删除用户。

    +

    首先,我们需要一张数据库表 users,用于存储数据。在 gino_fastapi_demo.models.users 模块中添加一个映射这张表的 model User

    +
    from . import db
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default="unnamed")
    +
    +
    +../_images/gino-fastapi-views.svg

    很简单的 model 定义,一切尽在不言中。

    +

    然后我们只需要在 API 的实现中正确使用它即可。创建一个新的 Python 二级包 gino_fastapi_demo.models.views,在其中添加一个模块 gino_fastapi_demo.views.users,内容为:

    +
    from fastapi import APIRouter
    +from pydantic import BaseModel
    +
    +from ..models.users import User
    +
    +router = APIRouter()
    +
    +@router.get("/users/{uid}")
    +async def get_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    return user.to_dict()
    +
    +class UserModel(BaseModel):
    +    name: str
    +
    +@router.post("/users")
    +async def add_user(user: UserModel):
    +    rv = await User.create(nickname=user.name)
    +    return rv.to_dict()
    +
    +@router.delete("/users/{uid}")
    +async def delete_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    await user.delete()
    +    return dict(id=uid)
    +
    +def init_app(app):
    +    app.include_router(router)
    +
    +
    +

    APIRouter 用来收集新接口的定义,然后在 init_app 里集成到 FastAPI 应用中去。这里我们加一点反转控制 :把接口做成模块化的,用 Entry Points 功能进行拼装,避免需要手动一一 import 将来可能有的其他接口。将下面的代码添加到 gino_fastapi_demo.main

    +
    import logging
    +from importlib.metadata import entry_points
    +
    +logger = logging.getLogger(__name__)
    +
    +def load_modules(app=None):
    +    for ep in entry_points()["gino_fastapi_demo.modules"]:
    +        logger.info("Loading module: %s", ep.name)
    +        mod = ep.load()
    +        if app:
    +            init_app = getattr(mod, "init_app", None)
    +            if init_app:
    +                init_app(app)
    +
    +
    +
    +

    提示

    +

    如果您的 Python 版本低于 3.8,您还需要这个 importlib-metadata 的移植

    +
    +

    然后在我们的应用工厂函数中调用它:

    +
     def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    +     db.init_app(app)
    ++    load_modules(app)
    +     return app
    +
    +
    +

    最后,根据 Poetry 插件文档,在 pyproject.toml 文件中定义 Entry Point:

    +
    [tool.poetry.plugins."gino_fastapi_demo.modules"]
    +"users" = "gino_fastapi_demo.views.users"
    +
    +
    +

    再执行一次 poetry install 来激活这些 Entry Point——这次您可能需要亲自重启 Uvicorn 的开发服务器了,因为自动重载机制无法识别 pyproject.toml 文件的变更。

    +

    现在您应该可以在 Swagger UI 中看到那 3 个新接口了,但是它们还都不能用,因为我们还没有创建数据库表。

    +
    +
    +

    集成 Alembic

    +

    请在项目顶层文件夹中执行下面的命令,以开始使用 Alembic

    +
    $ poetry run alembic init migrations
    +
    +
    +../_images/gino-fastapi-alembic.svg

    这句命令会生产一个新的文件夹 migrations,包含了 Alembic 用于数据库表结构变更追踪的版本文件。同时创建的还有一个在顶层文件夹下面的 alembic.ini 文件,我们把这些文件都添加到 Git 中。

    +

    为了能让 Alembic 用上我们用 GINO 定义的 model,我们需要修改 migrations/env.py 文件去链接 GINO 实例:

    +
     # add your model's MetaData object here
    + # for 'autogenerate' support
    + # from myapp import mymodel
    + # target_metadata = mymodel.Base.metadata
    +-target_metadata = None
    ++from gino_fastapi_demo.config import DB_DSN
    ++from gino_fastapi_demo.main import db, load_modules
    ++
    ++load_modules()
    ++config.set_main_option("sqlalchemy.url", str(DB_DSN))
    ++target_metadata = db
    +
    +
    +

    然后就可以创建我们的第一个变更版本了:

    +
    $ poetry run alembic revision --autogenerate -m 'add users table'
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.autogenerate.compare] Detected added table 'users'
    +  Generating migrations/versions/32c0feba61ea_add_users_table.py ...  done
    +
    +
    +

    生成的版本文件大体上应该长这样:

    +
    def upgrade():
    +    op.create_table(
    +        "users",
    +        sa.Column("id", sa.BigInteger(), nullable=False),
    +        sa.Column("nickname", sa.Unicode(), nullable=True),
    +        sa.PrimaryKeyConstraint("id"),
    +    )
    +
    +def downgrade():
    +    op.drop_table("users")
    +
    +
    +
    +

    提示

    +

    以后需要再次修改数据库表结构的时候,您只需要修改 GINO model 然后执行 alembic revision --autogenerate 命令来生成对应改动的新版本即可。提交前记得看一下生成的版本文件,有时需要调整。

    +
    +

    我们终于可以应用此次变更了,执行下面的命令将数据库表结构版本升级至最高:

    +
    $ poetry run alembic upgrade head
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.runtime.migration] Running upgrade  -> 32c0feba61ea, add users table
    +
    +
    +

    到这里,所有的接口应该都可以正常工作了,您可以在 Swagger UI 中试一下。

    +
    +
    +

    编写测试

    +

    为了不影响开发环境的数据库,我们需要为测试创建单独的数据库。根据下面的补丁修改 gino_fastapi_demo.config

    +
     config = Config(".env")
    +
    ++TESTING = config("TESTING", cast=bool, default=False)
    +
    + DB_DRIVER = config("DB_DRIVER", default="postgresql")
    + DB_HOST = config("DB_HOST", default=None)
    + DB_PORT = config("DB_PORT", cast=int, default=None)
    + DB_USER = config("DB_USER", default=None)
    + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    + DB_DATABASE = config("DB_DATABASE", default=None)
    ++if TESTING:
    ++    if DB_DATABASE:
    ++        DB_DATABASE += "_test"
    ++    else:
    ++        DB_DATABASE = "gino_fastapi_demo_test"
    + DB_DSN = config(
    +
    +
    +
    +

    提示

    +

    您需要执行 createdb 来创建数据库实例。比如说,如果您在 .env 文件中定义了 DB_DATABASE=mydb,那么测试数据库的名字就是 mydb_test。否则如果没定义的话,默认就是 gino_fastapi_demo_test

    +
    +

    然后在 tests/conftest.py 中创建 pytest fixture:

    +
    import pytest
    +from alembic.config import main
    +from starlette.config import environ
    +from starlette.testclient import TestClient
    +
    +environ["TESTING"] = "TRUE"
    +
    +@pytest.fixture
    +def client():
    +    from gino_fastapi_demo.main import db, get_app
    +
    +    main(["--raiseerr", "upgrade", "head"])
    +
    +    with TestClient(get_app()) as client:
    +        yield client
    +
    +    main(["--raiseerr", "downgrade", "base"])
    +
    +
    +../_images/gino-fastapi-tests.svg

    这个 fixture 的作用是,在跑测试之前创建所有的数据库表、提供一个 StarletteTestClient、并且在测试跑完之后删除所有的表及其数据,为后续测试保持一个干净的环境。

    +

    下面是一个简单的测试例子,tests/test_users.py

    +
    import uuid
    +
    +def test_crud(client):
    +    # create
    +    nickname = str(uuid.uuid4())
    +    r = client.post("/users", json=dict(name=nickname))
    +    r.raise_for_status()
    +
    +    # retrieve
    +    url = f"/users/{r.json()['id']}"
    +    assert client.get(url).json()["nickname"] == nickname
    +
    +    # delete
    +    client.delete(url).raise_for_status()
    +    assert client.get(url).status_code == 404
    +
    +
    +

    测试跑起来:

    +
    $ poetry run pytest
    +=========================== test session starts ===========================
    +platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    +rootdir: gino-fastapi-demo
    +collected 1 item
    +
    +tests/test_users.py .                                               [100%]
    +
    +============================ 1 passed in 1.21s ============================
    +
    +
    +
    +
    +

    生产环境注意事项

    +

    最近 Docker/Kubernetes 挺火,我们也写一个 Dockerfile

    +
    FROM python:3.8-alpine as base
    +
    +FROM base as builder
    +RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev
    +RUN pip install poetry
    +COPY . /src/
    +WORKDIR /src
    +RUN python -m venv /env && . /env/bin/activate && poetry install
    +
    +FROM base
    +RUN apk add --no-cache postgresql-libs
    +COPY --from=builder /env /env
    +COPY --from=builder /src /src
    +WORKDIR /src
    +CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"]
    +
    +
    +

    这个 Dockerfile 里,为了降低目标镜像文件的大小,我们分成了两步来分别进行源码构建和生产镜像的组装。另外,我们还采用了 Gunicorn 搭配 UvicornUvicornWorker 的方式来获取最佳生产级别可靠性。

    +

    回头看一下项目里一共有哪些文件。

    +../_images/gino-fastapi-layout.svg

    至此,我们就完成了演示项目的开发。下面是上生产可以用到的一个不完整检查清单:

    +
      +
    • DB_RETRY_LIMIT 设置成一个稍微大一点的数字,以支持在数据库就绪前启动应用服务器的情况。

    • +
    • migrations/env.py 中实现同样的重连尝试逻辑,这样 Alembic 也能拥有同样的特性。

    • +
    • 如果需要的话,启用 DB_SSL

    • +
    • 写一个 docker-compose.yml,用于其他开发人员快速尝鲜,甚至可以用于开发。

    • +
    • 启用持续集成 <CI_>,安装 pytest-cov 并且用 --cov-fail-under 参数来保障测试覆盖率。

    • +
    • 集成静态代码检查工具和安全性/CVE筛查工具。

    • +
    • 正确自动化 Alembic 的升级流程,比如在每次新版本部署之后执行。

    • +
    • 注意针对诸如 CSRFXSS 等常见攻击的安全性防护。

    • +
    • 编写压力测试。

    • +
    +

    最后再贴一次,实例程序的源码在这里,本教程的文档源码在`这里<https://github.com/python-gino/gino/blob/master/docs/tutorials/fastapi.rst>`__,请敞开了提 PR,修问题或者分享想法都行。祝玩得愉快!

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/1.1b2/tutorials/tutorial.html b/docs/zh/1.1b2/tutorials/tutorial.html new file mode 100644 index 0000000..42ee3b4 --- /dev/null +++ b/docs/zh/1.1b2/tutorials/tutorial.html @@ -0,0 +1,487 @@ + + + + + + + + GINO 基础教程 - GINO 1.1.0b2 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    GINO 基础教程

    +

    这是一篇写给刚入坑同学的指南,将介绍 GINO 的基本部分。阅读之前,请先了解以下知识点:

    + +

    您不需要对 SQLAlchemy 有所了解。

    +
    +

    介绍

    +

    简单来说,GINO 可以在您的异步应用中帮助您完成 SQL 语句的生成及执行,您只需要通过友好的对象化 API 来操作您的数据即可,无需亲自编写 SQL 与数据库交互。

    +

    因为异步编程并不会使您的程序变快——如果说不拖累的话——而且还会增加复杂度和风险,所以也许您并不需要 GINO 或者说是异步数据库连接。跳坑之前请先阅读为什么要用异步 ORM?

    +
    +
    +

    安装

    +

    请在终端中执行以下命令以安装 GINO:

    +
    $ pip install gino
    +
    +
    +

    以上就是安装 GINO 的推荐方式,因为这种方式始终会去安装最新的稳定版。

    +

    如果您还没有安装过 pip,您可以参阅 Python 安装指南

    +

    另外如果您在使用 Poetry 进行项目依赖关系管理,那需要执行的则是:

    +
    $ poetry add gino
    +
    +
    +
    +
    +

    声明模型

    +

    开始之前,我们需要先创建一个 Gino 的全局实例,通常叫做 db

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +

    db 可以被当做是数据库的一个代表,后续大部分的数据库交互都将通过它来完成。

    +

    “Model” 是 GINO 中的一个基本概念,它表示继承自 db.Model 的用户定义类。每个 Model 的子类代表了数据库中的一张表,而这些类的对象则代表了对应表中的一行数据。如果您曾经使用过其它 ORM 产品,对这种映射关系应该不感到陌生。现在我们尝试定义一个 model:

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +

    这里的 User 类其实就是在定义一张叫做 users 的数据库表,包含了 idnickname 两个字段。请注意,__tablename__ 是一个必要的固定属性。GINO 建议使用单数名词来为 model 命名,同时使用复数名词去命名表。每个 db.Column 属性都定义了一个数据库字段,其中第一个参数是字段类型,其余参数则用来定义字段其他属性或约束。您可以参考 SQLAlchemy 的文档来了解不同 db 类型到数据库类型的对应关系。

    +
    +

    注解

    +

    SQLAlchemy 是 Python 中一个强大的非异步 ORM 库,而 GINO 就是基于其构建的。通过不同的 SQL 方言实现,SQLAlchemy 支持包括 PostgreSQL 和 MySQL 在内的许多流行的 RDBMS,以至于有时相同的 Python 代码可以不经修改地运行在不同的数据库上。GINO 自然也承袭了这一特性,但目前暂仅支持 PostgreSQL(通过 asyncpg)。

    +
    +

    如果需要定义涵盖多个列的数据库约束或索引,您仍然可以通过 model 类属性的方式来定义,属性名称虽未被用到,但不能重复。例如:

    +
    class Booking(db.Model):
    +    __tablename__ = 'bookings'
    +
    +   day = db.Column(db.Date)
    +   booker = db.Column(db.String)
    +   room = db.Column(db.String)
    +
    +   _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey')
    +   _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True)
    +   _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room')
    +
    +
    +

    另外如果有倾向性,您也可以在 model 类之外定义约束和索引,请参考 SQLAlchemy 文档来了解更多细节。

    +

    由于一些限制,目前不允许在父类中直接使用类属性的方式来单独定义数据库约束和索引,__table_args__ 也是一样的。GINO 提供了 declared_attr() 来实现比如 mixin 类这样的功能,更多信息请参阅其 API 文档。

    +
    +
    +

    建立连接

    +

    前面的声明只是定义了映射关系,并非实际在数据库中创建了这些表结构。为了使用 GINO 来创建表,我们需要先与数据库建立连接。这里我们先为本指南创建一个 PostgreSQL 的数据库实例:

    +
    $ createdb gino
    +
    +
    +

    然后,告诉我们的 db 对象去连接这个数据库:

    +
    import asyncio
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +

    如果执行成功了,那就意味着您连上了新创建的数据库。此处的 postgresql 代表了要用的数据库方言(默认的驱动是 asyncpg,您也可以显式地指定使用它:postgresql+asyncpg:// 或者就只写 asyncpg://),localhost 是数据库服务器所在的地址,gino 是数据库实例的名字。这里可以读到更多关于如何构造一个数据库 URL 的信息。

    +
    +

    注解

    +

    在底层,set_bind() 调用了 create_engine() 来创建 engine,并将其绑定到 db 对象上。GINO engine 与 SQLAlchemy engine 类似,但 GINO engine 是异步的,而后者是阻塞式的。关于如何使用 engine,请参考 GINO 的 API 文档。

    +
    +

    建立连接之后,我们就可以用 GINO 在数据库中创建我们的表了(在同一个 main() 函数里):

    +
    await db.gino.create_all()
    +
    +
    +
    +

    警告

    +

    这里是 db.gino.create_all,而不是 db.create_all,因为 db 继承自 SQLAlchemy 的 MetaData,而 db.create_all 是 SQLAlchemy 的阻塞式方法,无法适用于绑定的 GINO engine。

    +

    实践中 create_all() 通常并不是一个理想的解决方案。为了管理数据库表结构,我们通常推荐使用诸如 Alembic 这样的工具,请参阅如何 使用 Alembic

    +
    +

    如果您想显式地断开与数据库的连接,您可以这么做:

    +
    await db.pop_bind().close()
    +
    +
    +

    继续之前,让我们重新看一下前面所有的代码:

    +
    import asyncio
    +from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +    await db.gino.create_all()
    +
    +    # further code goes here
    +
    +    await db.pop_bind().close()
    +
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +
    +
    +

    增删改查

    +

    为了操作数据库中的数据,GINO 提供了基本的基于对象的增删改查功能。

    +
    +

    +

    让我们从创建一个 User 对象开始:

    +
    user = await User.create(nickname='fantix')
    +# This will cause GINO to execute this SQL with parameter 'fantix':
    +# INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname
    +
    +
    +

    正如之前所说,user 对象代表了数据库中新插入的这一行数据。您可以通过 user 对象上的之前定义的列属性来访问每一列的值:

    +
    print(f'ID:       {user.id}')           # 1
    +print(f'Nickname: {user.nickname}')     # fantix
    +
    +
    +

    另外,您也可以先在内存中创建一个 user 对象,然后再将其插入到数据库中:

    +
    user = User(nickname='fantix')
    +user.nickname += ' (founder)'
    +await user.create()
    +
    +
    +
    +
    +

    +

    想要通过主键来获取一个 model 对象,您可以使用 model 的类方法 get()。比如,重新获取刚才插入的同一行数据:

    +
    user = await User.get(1)
    +# SQL (parameter: 1):
    +# SELECT users.id, users.nickname FROM users WHERE users.id = $1
    +
    +
    +

    常规的 SQL 查询则是通过类属性 query 来完成。比如,获取数据库中所有的 User 对象的列表:

    +
    all_users = await db.all(User.query)
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +

    或者,您也可以使用 querygino 扩展。比如,下面的代码可以实现一样的效果:

    +
    all_users = await User.query.gino.all()
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +
    +

    注解

    +

    实际上,User.query 是一个普通的 SQLAlchemy 查询对象,SQLAlchemy 的阻塞式执行方法依然存在其上,因此 GINO 向所有 SQLAlchemy 的“Executable”对象注入了一个 gino 扩展,以便在不影响 SQLAlchemy 原有 API 的基础上,让直接异步地执行这些查询对象更容易,而不用每次都通过 engine 或 db 对象来执行。

    +
    +

    现在让我们尝试增加一些过滤器。比如,查找出所有 ID 小于 10 的用户:

    +
    founding_users = await User.query.where(User.id < 10).gino.all()
    +# SQL (parameter: 10):
    +# SELECT users.id, users.nickname FROM users WHERE users.id < $1
    +
    +
    +

    因为查询对象就是出自于 SQLAlchemy core,所以请参阅如何编写查询

    +
    +

    警告

    +

    当您拿到一个 model 对象时,这个对象就已经彻底与数据库分离了,完全成为内存中的一个普通对象。这就意味着,即使数据库中对应的行发生了变化,对象的值仍然不会受到丝毫影响。类似地,如果您修改了该对象的值,数据库也不会受到任何影响。

    +

    并且,GINO 也不会追踪 model 对象,因此重复查询同一行数据将会得到两个独立的、拥有相同值的对象,修改其中一个的值不会幽灵般地影响到另一个的值。

    +

    不同于传统 ORM 的 model 对象通常是有状态的,GINO 的 model 对象则更像是用对象封装的 SQL 查询结果,这是 GINO 为了适应异步编程而特意设计的简易性,也是“GINO 不是 ORM”名字的来源。

    +
    +

    有时我们仅需要获取一个对象,比如验证登录时,使用用户名来查找一个用户。这时,可以使用这种便捷的写法:

    +
    user = await User.query.where(User.nickname == 'fantix').gino.first()
    +# SQL (parameter: 'fantix'):
    +# SELECT users.id, users.nickname FROM users WHERE users.nickname = $1
    +
    +
    +

    如果数据库中没有叫“fantix”的用户,则 user 会被置为 None

    +

    又有时,我们会需要获取一个单独的值,比如 ID 为 1 的用户的名字。此时可以使用 model 的类方法 select()

    +
    name = await User.select('nickname').where(User.id == 1).gino.scalar()
    +# SQL (parameter: 1):
    +# SELECT users.nickname FROM users WHERE users.id = $1
    +print(name)  # fantix
    +
    +
    +

    又比如,查询用户数量:

    +
    population = await db.func.count(User.id).gino.scalar()
    +# SQL:
    +# SELECT count(users.id) AS count_1 FROM users
    +print(population)  # 17 for example
    +
    +
    +
    +
    +

    +

    接下来,让我们尝试对数据做一些修改,下面的例子会穿插一些前面用过的查询操作。

    +
    # create a new user
    +user = await User.create(nickname='fantix')
    +
    +# get its name
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +assert name == user.nickname  # they are both 'fantix' before the update
    +
    +# modification here
    +await user.update(nickname='daisy').apply()
    +# SQL (parameters: 'daisy', 1):
    +# UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname
    +print(user.nickname)  # daisy
    +
    +# get its name again
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +print(name)  # daisy
    +assert name == user.nickname  # they are both 'daisy' after the update
    +
    +
    +

    这里的 update() 是我们碰到的第一个 model 实例上的 GINO 方法,它接受多个自定义命名参数,参数名对应着 model 的字段名,而参数值则为期望修改成的新的值。连着写的 apply() 则会将这一修改同步到数据库中。

    +
    +

    注解

    +

    GINO 显式地将“修改内存中对象的值”与“修改数据库中的行”拆分成了两个方法: update()apply()update() 负责修改内存中的值,并且将改动记录在返回的 UpdateRequest 对象中;紧接着调用的 UpdateRequest 对象的 apply() 方法则会将这些记录下的改动通过 SQL 更新到数据库中。

    +
    +
    +

    小技巧

    +

    UpdateRequest 对象还有一个方法也叫 update(),它与 model 对象上的 update() 方法的功能是一样的,只不过前者还会将新的改动记录与当前 UpdateRequest 已记录的改动合并在一起,并且返回同一个 UpdateRequest 对象。这意味着,您可以连着写多个 update() 调用,最后用一个 apply() 结尾,或者仅仅是通过 UpdateRequest 对象来完成内存对象的多次改动。

    +
    +

    Model 对象上的 update() 方法只能操作该对象对应的数据库中的一行数据,而如果您想要批量更新多行数据的话,您可以使用 model 类上的 update() 类方法。用法略有不同:

    +
    await User.update.values(nickname='Founding Member ' + User.nickname).where(
    +    User.id < 10).gino.status()
    +# SQL (parameter: 'Founding Member ', 10):
    +# UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2
    +
    +name = await User.select('nickname').where(
    +    User.id == 1).gino.scalar()
    +print(name)  # Founding Member fantix
    +
    +
    +

    这里不再有 UpdateRequest 了,所有的操作又回到了普通的 SQLAlchemy 用法,更多细节可以参考 SQLAlchemy 的文档

    +
    +
    +

    +

    最后,删除一行数据与更新一行数据有些类似,但要简单很多:

    +
    user = await User.create(nickname='fantix')
    +await user.delete()
    +# SQL (parameter: 1):
    +# DELETE FROM users WHERE users.id = $1
    +print(await User.get(user.id))  # None
    +
    +
    +
    +

    提示

    +

    还记得内存对象的事情吗?在最后一行的 print() 中,尽管数据库中已经没有这一行数据了,但是 user 对象依然在内存中,它的值也都没有变化,所以这里仍然可以用 user.id

    +
    +

    或者批量删除(千万不要忘记写 where!是不是整个表都不想要了?):

    +
    await User.delete.where(User.id > 10).gino.status()
    +# SQL (parameter: 10):
    +# DELETE FROM users WHERE users.id > $1
    +
    +
    +

    有了基本的 增删改查,您应该已经可以用 GINO 做出一些不可思议的东西来了。这篇上手指南到此结束,要了解更多请继续阅读文档的剩余部分。祝编程愉快!

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/index.html b/docs/zh/index.html new file mode 100644 index 0000000..a5b01d5 --- /dev/null +++ b/docs/zh/index.html @@ -0,0 +1,15 @@ + + + + + + + 页面重定向 + + + 如果您的浏览器没有自动跳转,请点击进入 GINO 文档。 + + + diff --git a/docs/zh/master/.buildinfo b/docs/zh/master/.buildinfo new file mode 100644 index 0000000..61c6568 --- /dev/null +++ b/docs/zh/master/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: ba1ea1eba6600e0e44e6427c32eceb21 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/zh/master/_images/263px-Minimum-Tonne.svg.png b/docs/zh/master/_images/263px-Minimum-Tonne.svg.png new file mode 100644 index 0000000..f431e31 Binary files /dev/null and b/docs/zh/master/_images/263px-Minimum-Tonne.svg.png differ diff --git a/docs/zh/master/_images/archlinux.webp b/docs/zh/master/_images/archlinux.webp new file mode 100644 index 0000000..3e358ef Binary files /dev/null and b/docs/zh/master/_images/archlinux.webp differ diff --git a/docs/zh/master/_images/community.png b/docs/zh/master/_images/community.png new file mode 100644 index 0000000..1aa9693 Binary files /dev/null and b/docs/zh/master/_images/community.png differ diff --git a/docs/zh/master/_images/community.svg b/docs/zh/master/_images/community.svg new file mode 100644 index 0000000..dae6507 --- /dev/null +++ b/docs/zh/master/_images/community.svg @@ -0,0 +1,26 @@ + + + + co-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/connection.png b/docs/zh/master/_images/connection.png new file mode 100644 index 0000000..e54598d Binary files /dev/null and b/docs/zh/master/_images/connection.png differ diff --git a/docs/zh/master/_images/docs.webp b/docs/zh/master/_images/docs.webp new file mode 100644 index 0000000..20259b1 Binary files /dev/null and b/docs/zh/master/_images/docs.webp differ diff --git a/docs/zh/master/_images/engine.png b/docs/zh/master/_images/engine.png new file mode 100644 index 0000000..6b64561 Binary files /dev/null and b/docs/zh/master/_images/engine.png differ diff --git a/docs/zh/master/_images/exchangeratesapi.webp b/docs/zh/master/_images/exchangeratesapi.webp new file mode 100644 index 0000000..dc2657a Binary files /dev/null and b/docs/zh/master/_images/exchangeratesapi.webp differ diff --git a/docs/zh/master/_images/explanation.png b/docs/zh/master/_images/explanation.png new file mode 100644 index 0000000..1de02e2 Binary files /dev/null and b/docs/zh/master/_images/explanation.png differ diff --git a/docs/zh/master/_images/explanation.svg b/docs/zh/master/_images/explanation.svg new file mode 100644 index 0000000..ef775d1 --- /dev/null +++ b/docs/zh/master/_images/explanation.svg @@ -0,0 +1,19 @@ + + + + ex-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-alembic.svg b/docs/zh/master/_images/gino-fastapi-alembic.svg new file mode 100644 index 0000000..0cfc4b1 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-alembic.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    alembic.ini
    ale...
    migrations
    migra...
    env.py
    env...
    versions
    versi...
    32c0feba61ea_add_users_table.py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-config.svg b/docs/zh/master/_images/gino-fastapi-config.svg new file mode 100644 index 0000000..673d084 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-config.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    config.py
    con...
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-env.svg b/docs/zh/master/_images/gino-fastapi-env.svg new file mode 100644 index 0000000..4ec9e4e --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-env.svg @@ -0,0 +1,3 @@ + + +
    .env
    .env
    gino-fastapi-demo
    gino-...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-layout.svg b/docs/zh/master/_images/gino-fastapi-layout.svg new file mode 100644 index 0000000..07f4701 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-layout.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    .env
    .env
    models
    models
    __init__.py
    __i...
    users.py
    use...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    __init__.py
    __i...
    asgi.py
    asg...
    config.py
    con...
    main.py
    mai...
    tests
    tests
    conftest.py
    con...
    test_users.py
    tes...
    migrations
    migra...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    The project root directory.

    Alembic data directory.

    Database migration revisions directory.

    One of the revisions.

    Alembic Python environment.

    Application source code container.

    Project root python package.

    Database models and GINO instance.

    GINO instance (SQLAlchemy Metadata).

    Models for users.

    API implementation.



    User-related APIs.



    ASGI entry point.

    Starlette-style application configuration.

    Application initialization.

    Testing code.

    pytest fixtures.

    User-related tests.

    Local config, not in Git control.

    Alembic entry config.

    Production Docker image.

    Poetry-frozen dependency versions.

    Project and dependency definition.
    The project root directory....
    alembic.ini
    ale...
    Dockerfile
    Doc...
    env.py
    env...
    versions
    versi...
    32c0feba61ea....py
    32c...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-models-users.svg b/docs/zh/master/_images/gino-fastapi-models-users.svg new file mode 100644 index 0000000..886d581 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-models-users.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-models.svg b/docs/zh/master/_images/gino-fastapi-models.svg new file mode 100644 index 0000000..4dc983c --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-models.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    models
    models
    __init__.py
    __i...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-poetry.svg b/docs/zh/master/_images/gino-fastapi-poetry.svg new file mode 100644 index 0000000..e0da8c1 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-poetry.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    pyproject.toml
    pyp...
    poetry.lock
    poe...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-src.svg b/docs/zh/master/_images/gino-fastapi-src.svg new file mode 100644 index 0000000..7310163 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-src.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    src
    src
    gino_fastapi_demo
    gino_...
    __init__.py
    __i...
    asgi.py
    asg...
    main.py
    mai...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-tests.svg b/docs/zh/master/_images/gino-fastapi-tests.svg new file mode 100644 index 0000000..53673d8 --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-tests.svg @@ -0,0 +1,3 @@ + + +
    gino-fastapi-demo
    gino-...
    tests
    tests
    test_users.py
    tes...
    conftest.py
    con...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi-views.svg b/docs/zh/master/_images/gino-fastapi-views.svg new file mode 100644 index 0000000..7321a7a --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi-views.svg @@ -0,0 +1,3 @@ + + +
    src
    src
    gino_fastapi_demo
    gino_...
    views
    views
    __init__.py
    __i...
    users.py
    use...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino-fastapi.svg b/docs/zh/master/_images/gino-fastapi.svg new file mode 100644 index 0000000..9d365ba --- /dev/null +++ b/docs/zh/master/_images/gino-fastapi.svg @@ -0,0 +1,3 @@ + + +
    Gunicorn
    Gunicorn
    Uvicorn
    Uvicorn
    Starlette
    Starlette
    FastAPI
    FastAPI
    API Implementation
    API Implementation
    GINO Models
    GINO Models
    GINO with Starlette
    GINO with Starlette
    SQLAlchemy core
    SQLAlchemy core
    asyncpg
    asyncpg
    Alembic
    Alembic
    psycopg2
    psycopg2
    PostgreSQL
    PostgreSQL
    Application Server
    Application Server
    ASGI Middleware
    ASGI Middleware
    Web Framework
    Web Framework
    Tutorial Code
    Tutorial Code
    GINO
    GINO
    Database Library
    Database Library
    HTTP API
    HTTP API
    DB Migration CLI
    DB Migration CLI
    Legend
    Legend
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/docs/zh/master/_images/gino.png b/docs/zh/master/_images/gino.png new file mode 100644 index 0000000..9032a58 Binary files /dev/null and b/docs/zh/master/_images/gino.png differ diff --git a/docs/zh/master/_images/github.png b/docs/zh/master/_images/github.png new file mode 100644 index 0000000..7b91181 Binary files /dev/null and b/docs/zh/master/_images/github.png differ diff --git a/docs/zh/master/_images/github.svg b/docs/zh/master/_images/github.svg new file mode 100644 index 0000000..cb2484b --- /dev/null +++ b/docs/zh/master/_images/github.svg @@ -0,0 +1,27 @@ + + + + sourcecode + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/happy-hacking.png b/docs/zh/master/_images/happy-hacking.png new file mode 100644 index 0000000..701e7bd Binary files /dev/null and b/docs/zh/master/_images/happy-hacking.png differ diff --git a/docs/zh/master/_images/how-to.png b/docs/zh/master/_images/how-to.png new file mode 100644 index 0000000..b643424 Binary files /dev/null and b/docs/zh/master/_images/how-to.png differ diff --git a/docs/zh/master/_images/how-to.svg b/docs/zh/master/_images/how-to.svg new file mode 100644 index 0000000..7461024 --- /dev/null +++ b/docs/zh/master/_images/how-to.svg @@ -0,0 +1,22 @@ + + + + guide + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/open-source.png b/docs/zh/master/_images/open-source.png new file mode 100644 index 0000000..f2327c0 Binary files /dev/null and b/docs/zh/master/_images/open-source.png differ diff --git a/docs/zh/master/_images/open-source.svg b/docs/zh/master/_images/open-source.svg new file mode 100644 index 0000000..0946f0a --- /dev/null +++ b/docs/zh/master/_images/open-source.svg @@ -0,0 +1,18 @@ + + + + bsd + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/python-gino.webp b/docs/zh/master/_images/python-gino.webp new file mode 100644 index 0000000..f6c11ac Binary files /dev/null and b/docs/zh/master/_images/python-gino.webp differ diff --git a/docs/zh/master/_images/python.png b/docs/zh/master/_images/python.png new file mode 100644 index 0000000..bb01dbc Binary files /dev/null and b/docs/zh/master/_images/python.png differ diff --git a/docs/zh/master/_images/python.svg b/docs/zh/master/_images/python.svg new file mode 100644 index 0000000..f62b29b --- /dev/null +++ b/docs/zh/master/_images/python.svg @@ -0,0 +1,22 @@ + + + + download + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/reference.png b/docs/zh/master/_images/reference.png new file mode 100644 index 0000000..fdc4dae Binary files /dev/null and b/docs/zh/master/_images/reference.png differ diff --git a/docs/zh/master/_images/reference.svg b/docs/zh/master/_images/reference.svg new file mode 100644 index 0000000..0c5f656 --- /dev/null +++ b/docs/zh/master/_images/reference.svg @@ -0,0 +1,19 @@ + + + + re-icon + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/tutorials.png b/docs/zh/master/_images/tutorials.png new file mode 100644 index 0000000..b87dbfd Binary files /dev/null and b/docs/zh/master/_images/tutorials.png differ diff --git a/docs/zh/master/_images/tutorials.svg b/docs/zh/master/_images/tutorials.svg new file mode 100644 index 0000000..2eb2111 --- /dev/null +++ b/docs/zh/master/_images/tutorials.svg @@ -0,0 +1,26 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_images/why_coroutine.png b/docs/zh/master/_images/why_coroutine.png new file mode 100644 index 0000000..99c743a Binary files /dev/null and b/docs/zh/master/_images/why_coroutine.png differ diff --git a/docs/zh/master/_images/why_multicore.png b/docs/zh/master/_images/why_multicore.png new file mode 100644 index 0000000..c209a9f Binary files /dev/null and b/docs/zh/master/_images/why_multicore.png differ diff --git a/docs/zh/master/_images/why_multithreading.png b/docs/zh/master/_images/why_multithreading.png new file mode 100644 index 0000000..e0e4321 Binary files /dev/null and b/docs/zh/master/_images/why_multithreading.png differ diff --git a/docs/zh/master/_images/why_single_task.png b/docs/zh/master/_images/why_single_task.png new file mode 100644 index 0000000..54fab08 Binary files /dev/null and b/docs/zh/master/_images/why_single_task.png differ diff --git a/docs/zh/master/_images/why_throughput.png b/docs/zh/master/_images/why_throughput.png new file mode 100644 index 0000000..388f533 Binary files /dev/null and b/docs/zh/master/_images/why_throughput.png differ diff --git a/docs/zh/master/_sources/explanation.rst.txt b/docs/zh/master/_sources/explanation.rst.txt new file mode 100644 index 0000000..6d91357 --- /dev/null +++ b/docs/zh/master/_sources/explanation.rst.txt @@ -0,0 +1,7 @@ +Explanation +=========== + +.. toctree:: + :glob: + + explanation/* diff --git a/docs/zh/master/_sources/explanation/async.rst.txt b/docs/zh/master/_sources/explanation/async.rst.txt new file mode 100644 index 0000000..a857170 --- /dev/null +++ b/docs/zh/master/_sources/explanation/async.rst.txt @@ -0,0 +1,214 @@ +Asynchronous Programming 101 +============================ + + +The Story +--------- + +Let's say we want to build a search engine. We'll use a single core computer to +build our index. To make things simpler, our tasks are to fetch web pages +(I/O operation), and process their content (CPU operation). Each task looks +like this: + +.. image:: ../images/why_single_task.png + :align: center + +We have lots of web pages to index, so we simply handle them one by one: + +.. image:: ../images/why_throughput.png + :align: center + +Let's assume the time of each task is constant: each second, 2 tasks are done. +Thus we can say what the throughput of the current system is 2 tasks/sec. How +can we improve the throughput? An obvious answer is to add more CPU cores: + +.. image:: ../images/why_multicore.png + :align: center + +This simply doubles our throughput to 4 tasks/sec, and linearly scales as we +add more CPU cores, if the network is not a bottleneck. But can we improve +the throughput for each CPU core? The answer is yes, we can use +multi-threading: + +.. image:: ../images/why_multithreading.png + :align: center + +Wait a second! The 2 threads barely finished 6 tasks in 2 seconds, a +throughput of only 2.7 tasks/sec, much lower than 4 tasks/sec with 2 cores. +What's wrong with multi-threading? From the diagram we can see: + +* There are yellow bars taking up extra time. +* The green bars can still overlap with any bar in the other thread, but +* non-green bars cannot overlap with non-green bars in the other thread. + +The yellow bars are time taken by `context switches +`_, a necessary part of allowing +multiple threads or processes to run on a single CPU core concurrently. +One CPU core can do only one thing at a time (let's assume a world without +`Hyper-threading `_ or similar), +so in order to run several threads concurrently the CPU must `split its +time `_ into small +slices, and run a little bit of each thread within these slices. The yellow bar +is the overhead for the CPU to switch context to run a different thread. The +scale is a bit dramatic, but it helps with the point. + +Wait again here, the green bars are overlapping between threads. Is the CPU +doing two things at the same time? No, the CPU is doing nothing in the middle +of the green bar, because it's waiting for the HTTP response (I/O). That's how +multi-threading could improve the throughput to 2.7 tasks/sec, instead of +decreasing it to 1.7 tasks/sec. You may try in real to run CPU-intensive +tasks with multi-threading on single core, there won't be any improvement. Like +the multiplexed red bars (in practice there might be more context switches +depending on the task), they appear to be running at the same time, but the +total time for all to finish is actually longer than running the tasks one +by one. That's also why this is called concurrency instead of parallelism. + +As you might imagine, throughput will improve less with each additional thread, +until throughput begins to decrease because context switches are wasting too +much time, not to mention the extra memory footprint taken by new threads. It +is usually not practical to have tens of thousands of threads running on a single +CPU core. How, then, is it possible to have tens of thousands of I/O-bound tasks +running concurrently on a single CPU core? This is the once-famous `C10k +problem `_, usually solved by +asynchronous I/O: + +.. image:: ../images/why_coroutine.png + :align: center + +.. note:: + + Asynchronous I/O and coroutines are two different things, but they usually + go together. Here we will stick with coroutines for simplicity. + +Awesome! The throughput is 3.7 tasks/sec, nearly as good as 4 tasks/sec of 2 +CPU cores. Though this is not real data, compared to OS threads coroutines +do take much less time to context switch and have a lower memory footprint, +thus making them an ideal option for the C10k problem. + + +Cooperative multitasking +------------------------ + +So what is a coroutine? + +In the last diagram above, you may have noticed a difference compared to the +previous diagrams: the green bars are overlapping within the same thread. +That is because the in the last diagram, our code is using asynchronous I/O, +whereas the previously we were using blocking I/O. As the name suggests, blocking +I/O will block the thread until the I/O result is ready. Thus, there can be only +one blocking I/O operation running in a thread at a time. To achieve concurrency +with blocking I/O, either multi-threading or multi-processing must be used. +In contrast, asynchronous I/O allows thousands (or even more) of concurrent +I/O reads and writes within the same thread, with each I/O operation blocking +only the coroutine performing the I/O rather than the whole thread. Like +multi-threading, coroutines provide a means to have concurrency during I/O, +but unlike multi-threading this concurrency occurs within a single thread. + +Threads are scheduled by the operating system using an approach called `preemptive +multitasking `_. For +example, in previous multi-threading diagram there was only one CPU core. When +Thread 2 tried to start processing the first web page content, Thread 1 hadn't +finished processing its own. The OS brutally interrupted Thread 1 and shared +some resource (time) for Thread 2. But Thread 1 also needed CPU time to finish +its processing at the same time, so in turn after a while the OS had to pause +Thread 2 and resume Thread 1. Depending on the size of the task, such turns may +happen several times, so that every thread may have a fair chance to run. It is +something like this: + +.. code-block:: none + + Thread 1: I wanna run! + OS: Okay, here you go... + Thread 2: I wanna run! + OS: Urh, alright one sec ... Thread 1, hold on for a while! + Thread 1: Well I'm not done yet, but you are the boss. + OS: It won't be long. Thread 2 it's your turn now. + Thread 2: Yay! (&%#$@..+*&#) + Thread 1: Can I run now? + OS: Just a moment please ... Thread 2, give it a break! + Thread 2: Alright ... but I really need the CPU. + OS: You'll have it later. Thread 1, hurry up! + +In contrast, coroutines are scheduled by themselves cooperatively with the help +of an event manager. The event manager lives in the same thread as the +coroutines and unlike the OS scheduler that forces context switches on threads, +the event manager acts only when coroutines pause themselves. A thread knows +when it wants to run, but coroutines don't - only the event manager knows which +coroutine should run. The event manager may only trigger the next coroutine to +run after the previous coroutine yields control to wait for an event (e.g. +wait for an HTTP response). This approach to achieve concurrency is called +`cooperative multitasking +`_. It's like this: + +.. code-block:: none + + Coroutine 1: Let me know when event A arrives. I'm done here before that. + Event manager: Okay. What about you, coroutine 2? + Coroutine 2: Um I've got nothing to do here before event B. + Event manager: Cool, I'll be watching. + Event manager: (after a while) Hey coroutine 1, event A is here! + Coroutine 1: Awesome! Let me see ... looks good, but I need event C now. + Event manager: Very well. Seems event B arrived just now, coroutine 2? + Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done. + Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while. + (silence) + Event manager: Damn, event C timed out! + Coroutine 1: Arrrrh gotta kill myself with an exception :S + Event manager: Up to you :/ + +For coroutines, a task cannot be paused externally, the task can only pause +itself from within. When there are a lot of coroutines, concurrency depends on +each of them pausing from time to time to wait for events. If you wrote a +coroutine that never paused, it would allow no concurrency at all when running +because no other coroutine would have a chance to run. On the other hand, you +can feel safe in the code between pauses, because no other coroutine can +run at the same time to mess up shared states. That's why in the last diagram, +the red bars are not interleaved like threads. + +.. tip:: + + In Python and asyncio, ``async def`` declares coroutines, ``await`` yields + control to event loop (event manager). + + +Pros and cons +------------- + +Asynchronous I/O may handle tens of thousands of concurrent I/O operations in +the same thread. This can save a lot of CPU time from context switching, and +memory from multi-threading. Therefore if you are dealing with lots of I/O-bound +tasks concurrently, asynchronous I/O can efficiently use limited CPU and memory to +deliver greater throughput. + +With coroutines, you can naturally write sequential code that is cooperatively +scheduled. If your business logic is complex, coroutines could greatly improve +readability of asynchronous I/O code. + +However for a single task, asynchronous I/O can actually impair throughput. For +example, for a simple ``recv()`` operation blocking I/O would just block until +returning the result, but for asynchronous I/O additional steps are required: +register for the read event, wait until event arrives, try to ``recv()``, repeat +until a result returns, and finally feed the result to a callback. With coroutines, +the framework cost is even larger. Thanks to uvloop_ this cost has been minimized +in Python, but it is still additional overhead compared to raw blocking I/O. + +Timing in Asynchronous I/O is also less predictable because of its cooperative +nature. For example, in a coroutine you may want to sleep for 1 second. However, +if another coroutine received control and ran for 2 seconds, by the time we get +back to the first coroutine 2 seconds have already passed. Therefore, ``sleep(1)`` +means to wait for at least 1 second. In practice, you should try your best to make +sure that all code between ``await`` finishes ASAP, being literally cooperative. +Still, there can be code outside your control, so it is important to keep this +unpredictibility of timing in mind. + +Finally, asynchronous programming is complicated. Writing good asynchronous code +is easier said than done, and debugging it is more difficult than debugging +similar synchronous code. Especially when a whole team is working on the +same piece of asynchronous code, it can easily go wrong. Therefore, a general +suggestion is to use asynchronous I/O carefully for I/O-bound high concurrency +scenarios only. It's not a drop-in that will provide a performance boost, but +more like a sharp blade for concurrency with two edges. And if you are dealing with +time-critical tasks, think again to be sure. + + +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/zh/master/_sources/explanation/engine.rst.txt b/docs/zh/master/_sources/explanation/engine.rst.txt new file mode 100644 index 0000000..ebd59ed --- /dev/null +++ b/docs/zh/master/_sources/explanation/engine.rst.txt @@ -0,0 +1,521 @@ +===================== +Engine and Connection +===================== + +:class:`~gino.engine.GinoEngine` is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together: + +.. image:: ../images/engine.png + :align: center + +Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users. + +During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use. + +.. note:: + + In SQLAlchemy, database drivers are supposed to follow the DB-API standard, + which does not usually provide a pool implementation. Therefore, SQLAlchemy + has its own pool implementation, created directly in engine. This is where + this diagram doesn't fit SQLAlchemy. + +The pool creates raw connections, not the :class:`~gino.engine.GinoConnection` +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries. + +On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use. + +.. note:: + + Another difference to SQLAlchemy here: GINO execution methods always return + final results, while in SQLAlchemy accessing the result may cause further + implicit database accesses. Therefore GINO engine immediately releases the + connection when the execution method on the engine returns, but SQLAlchemy + can only release the connection implicitly when the result data is found + exhausted. + + By immediately releasing a connection, GINO may not release the related raw + connection when the raw connection was reused from another parent + connection. We'll get to this later. + +GINO also supports `implicit execution +`_ +without having to specify an engine or connection explicitly. This is done by +binding the engine to the ``db`` instance, also known as the +:class:`~sqlalchemy.schema.MetaData` or the :class:`~gino.api.Gino` instance. +You may possibly bind a :class:`~gino.engine.GinoConnection` instance, but that +is greatly not recommended because it is very much untested. + +At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +``db`` instance on creation, therefore the models can do implicit execution too +if their ``db`` has a bind. + +Then let's get to some details. + + +Creating Engines +---------------- + +GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous :class:`~gino.engine.GinoEngine` is +just ``gino``, but only available after ``gino`` is imported:: + + import gino, sqlalchemy + + async def main(): + e = await sqlalchemy.create_engine('postgresql://...', strategy='gino') + # e is a GinoEngine + +.. tip:: + + Please read `this SQLAlchemy document + `_ + to learn about writing database URLs. + +Also the GINO strategy replaces the default driver of dialect ``postgresql://`` +from ``psycopg2`` to ``asyncpg``, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +``postgresql+asyncpg://...`` or just ``asyncpg://...``. + +GINO also offers a shortcut as :func:`gino.create_engine`, which only sets the +default strategy to ``gino`` and does nothing more. So here is an identical +example:: + + import gino + + async def main(): + e = await gino.create_engine('postgresql://...') + # e is also a GinoEngine + +As you may have noticed, when using the GINO strategy, +:func:`~sqlalchemy.create_engine` returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default. + +For it is just SQLAlchemy :func:`~sqlalchemy.create_engine`, the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!): + +For Dialect: + +* `isolation_level `_ +* `paramstyle `_ + +For Engine: + +* `echo `_ +* `execution_options `_ +* `logging_name `_ + +While these parameters are discarded by GINO: + +* `module `_ + +In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from :func:`~asyncpg.pool.create_pool`. +For example, we can create an engine without initial connections:: + + e = await gino.create_engine('postgresql://...', min_size=0) + +Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:: + + import sqlalchemy + + metadata = sqlalchemy.MetaData() + metadata.bind = 'postgresql://...' + + # or in short + + metadata = sqlalchemy.MetaData('postgresql://...') + +This implicitly calls :func:`~sqlalchemy.create_engine` under the hood. However +in GINO, creating an engine requires ``await``, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass :class:`~gino.api.Gino`, reverted it to simple assignment:: + + import gino + + db = gino.Gino() + + async def main(): + # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind + engine = await gino.create_engine('postgresql://...') + db.bind = engine + +And provided a shortcut to do so:: + + engine = await db.set_bind('postgresql://...') + +And another simpler shortcut for one-time usage:: + + db = await gino.Gino('postgresql://...') + +To unset a bind and close the engine:: + + engine, db.bind = db.bind, None + await engine.close() + +Or with a shortcut correspondingly:: + + await engine.pop_bind().close() + +Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +Managing Connections +-------------------- + +With a :class:`~gino.engine.GinoEngine` at hand, you can acquire connections +from the pool now:: + + conn = await engine.acquire() + +Don't forget to release it after use:: + + await conn.release() + +Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:: + + async with engine.acquire() as conn: + # play with the connection + +Here ``conn`` is a :class:`~gino.engine.GinoConnection` instance. As mentioned +previously, :class:`~gino.engine.GinoConnection` is mapped to an underlying raw +connection, as shown in following diagram: + +.. image:: ../images/connection.png + :align: center + +Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: ``reuse`` and +``lazy``. They are keyword arguments on :meth:`~gino.engine.GinoEngine.acquire` +and by default switched off. + +reuse +""""" + +When acquiring a :class:`~gino.engine.GinoConnection` (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +:class:`~gino.engine.GinoConnection` (2). This is the default behavior of +:meth:`~gino.engine.GinoConnection.acquire` with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:: + + async with engine.acquire() as conn1: + async with engine.acquire() as conn2: + # conn2 is a completely different connection than conn1 + +But sometimes ``conn2`` may exist in a different method:: + + async def outer(): + async with engine.acquire() as conn1: + await inner() + + async def inner(): + async with engine.acquire() as conn2: + # ... + +And we probably wish ``inner`` could reuse the same raw connection in +``outer`` to save some resource, or borrow a new one if ``inner`` is +individually called without ``outer``:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(conn2=None): + if conn2 is None: + async with engine.acquire() as conn2: + # ... + else: + # the same ... again + +This is exactly the scenario ``reuse`` could be useful. We can simply tell the +:meth:`~gino.engine.GinoConnection.acquire` to reuse the most recent reusable +connection in current context by setting ``reuse=True``, as presented in this +identical example:: + + async def outer(): + async with engine.acquire() as conn1: + await inner(conn1) + + async def inner(): + async with engine.acquire(reuse=True) as conn2: + # ... + +Back to previous diagram, the blue :class:`~gino.engine.GinoConnection` +instances (3, 4, 6) are "reusing connections" acquired with ``reuse=True``, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that ``acquire(reuse=True)`` always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack. + +.. tip:: + + By context, we are actually referring to the context concept in + `contextvars `_ the + new module in Python 3.7, and its partial backport `aiocontextvars + `_. Simply speaking, you may + treat a series of function calls in a chain as in the same context, even if + there is an ``await``. It's something like a thread local in asyncio. + +:class:`~gino.engine.GinoConnection` (2) may be created through +``acquire(reuse=True)`` too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection. + +Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +:class:`~gino.engine.GinoConnection` can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more. + +lazy +"""" + +As you may have found, :class:`~gino.engine.GinoConnection` (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set ``lazy=True`` on acquire. + +A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, :class:`~gino.engine.GinoConnection` (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + +On implementation level, ``lazy`` is extremely easy in +:meth:`~gino.engine.GinoEngine.acquire`: if ``lazy=False`` then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, :class:`~gino.egnine.GinoConnection` will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all :class:`~gino.engine.GinoConnection` +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:: + + async with engine.acquire(lazy=True) as conn: # (7) + await conn.scalar('select now()') # (8) + await conn.release(permanent=False) # release (8) + await asyncio.sleep(10) # simulate long I/O work + await conn.scalar('select now()') # re-acquire a new raw connection, + # not necessarily the same (8) + +When used together with ``reuse``, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set ``lazy=False`` on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again: + +.. image:: ../images/connection.png + :align: center + + +reusable +"""""""" + +Usually, you don't have to worry about the two options ``reuse`` and ``lazy``, +using the default :meth:`~gino.engine.GinoEngine.acquire` will always create +a concrete :class:`~gino.engine.GinoConnection` with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use ``reusable=False`` on acquire. As shown in the diagram, the unreusable +:class:`~gino.engine.GinoConnection` is an orphan away from any stack:: + + async with engine.acquire(): # (2) + async with engine.acquire(reusable=False): # the unreusable connection + async with engine.acquire(reuse=True): # (3) + +Unreusable connections can be lazy. But it is usually meaningless to specify +both ``reuse=True`` and ``reusable=False`` at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +``acquire(reuse=True, reusable=False)`` unless you know what it does. + + +current_connection +"""""""""""""""""" + +Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +:attr:`~gino.engine.GinoEngine.current_connection` which is always the reusable +:class:`~gino.engine.GinoConnection` at the top of current stack, or ``None`` +if current stack is empty. + +.. tip:: + + The different between :attr:`~gino.engine.GinoEngine.current_connection` + and :meth:`acquire(reuse=True) ` is, the + latter always produces a :class:`~gino.engine.GinoConnection`, while the + former may not. + + +Executing Queries +----------------- + +Once you have a :class:`~gino.engine.GinoConnection` instance, you can start +executing queries with it. There are 6 variants of the execute method: +:meth:`~gino.engine.GinoConnection.all`, +:meth:`~gino.engine.GinoConnection.first`, +:meth:`~gino.engine.GinoConnection.one`, +:meth:`~gino.engine.GinoConnection.one_or_none`, +:meth:`~gino.engine.GinoConnection.scalar` and +:meth:`~gino.engine.GinoConnection.status`. They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results: + +* :meth:`~gino.engine.GinoConnection.all` returns all results in a + :class:`list`, which may be empty when the query has no result, empty but + still a :class:`list`. +* :meth:`~gino.engine.GinoConnection.first` returns the first result directly, + or ``None`` if there is no result at all. There is usually some optimization + behind the scene to efficiently get only the first result, instead of loading + the full result set into memory. +* :meth:`~gino.engine.GinoConnection.one` returns exactly one result. If there + is no result at all or if there are multiple results, an exception is raised. +* :meth:`~gino.engine.GinoConnection.one_or_none` is similar to + :meth:`~gino.engine.GinoConnection.one`, but it returns ``None`` if there is + no result instead or raising an exception. +* :meth:`~gino.engine.GinoConnection.scalar` is similar to + :meth:`~gino.engine.GinoConnection.first`, it returns the first value of the + first result. Quite convenient to just retrieve a scalar value from database, + like ``NOW()``, ``MAX()``, ``COUNT()`` or whatever generates a single value. + ``None`` is also returned when there is no result, it is up to you how to + distinguish no result and the first value is ``NULL``. +* :meth:`~gino.engine.GinoConnection.status` executes the query and discard all + the query results at all. Instead it returns the execution status line as it + is, usually a textual string. Note, there may be no optimization to only + return the status without loading the results, so make your query generate + nothing if you don't want any result. + +By "result", I meant :class:`~sqlalchemy.engine.RowProxy` of SQLAlchemy - an +immutable row instance with both :class:`tuple` and :class:`dict` interfaces. +Database values are translated twice before they are eventually stored in a +:class:`~sqlalchemy.engine.RowProxy`: first by the database driver (dialect) +from network payload to Python objects (see `Type Conversion +`_ of +how asyncpg does this), second by SQLAlchemy +:meth:`~sqlalchemy.types.TypeEngine.result_processor` depending on the actual +type and dialect. + +The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy :meth:`~sqlalchemy.engine.Connection.execute` (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +``multiparams``, all 4 methods will always return ``None`` discarding all +results. Likewise, the parameter values are processed twice too: first by +:meth:`~sqlalchemy.types.TypeEngine.bind_processor` then the database driver. + +GINO also supports SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execution_options` provided either on +:meth:`engine level `, +:meth:`connection level ` or on +:meth:`queries `. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling ``return_model`` and providing a ``model`` will make +:meth:`~gino.engine.GinoConnection.all` and +:meth:`~gino.engine.GinoConnection.first` return ORM model instance(s) instead +of :class:`~sqlalchemy.engine.RowProxy` instance(s). See also +:meth:`~sqlalchemy.engine.Connection.execution_options` for more information. + +In addition, GINO has an :meth:`~gino.engine.GinoConnection.iterate` method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a `server-side cursor +`_. + + +Implicit Execution +------------------ + +Acquire a :class:`~gino.engine.GinoConnection` and execute queries on it, that +is the most explicit way. You can also execute queries on a +:class:`~gino.engine.GinoEngine` instance. In this case, a connection will be +acquired with ``reuse=True`` for you implicitly, and released after returning:: + + await engine.scalar('select now()') + +Equals to:: + + async with engine.acquire(reuse=True) as conn: + await conn.scalar('select now()') + +This allows you to easily write connectionless code. For example:: + + async def get_now(): + return await engine.scalar('select now()') + + async def main(): + async with engine.acquire(): + now = await get_now() + await engine.status('UPDATE ...') + +In this example, ``main()`` will take only one raw connection. ``get_now()`` +can also work alone out of any ``acquire()`` context, thanks to ``reuse``. + +Furthermore, GINO provides the same query APIs on :class:`~gino.api.Gino` +directly. They are simply delegates to corresponding API methods on the +``bind``. This allows even engine-less programming:: + + db = gino.Gino() + + async def get_now(): + return await db.scalar('select now()') + + async def main(): + async with db.with_bind('postgresql://...'): + now = await get_now() + await db.status('UPDATE ...') + +.. note:: + + In this example we didn't put the two queries in an ``acquire()`` block, so + they might be executed in two different connections. + +At last, the SQLAlchemy `implicit execution +`_ +on queries also work in GINO, under an extension named ``gino``:: + + await users_table.select().gino.all() + +By default, the extension :class:`~gino.api.GinoExecutor` is injected on +:class:`~sqlalchemy.sql.expression.Executable` as a property of name ``gino`` +at the creation of :class:`~gino.api.Gino` instance. Therefore, any +:class:`~sqlalchemy.sql.expression.Executable` object has the ``gino`` +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the ``bind`` of the ``db`` instance. diff --git a/docs/zh/master/_sources/explanation/sa20.rst.txt b/docs/zh/master/_sources/explanation/sa20.rst.txt new file mode 100644 index 0000000..0c18c1f --- /dev/null +++ b/docs/zh/master/_sources/explanation/sa20.rst.txt @@ -0,0 +1,691 @@ +SQLAlchemy 2.0 +============== + +This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes. + +`SQLAlchemy 2.0 `__ will +deliver many breaking API changes, and `SQLAlchemy 1.4 +`__ will be the "interim" +version for people to eventually upgrade their software to use SQLAlchemy 2.0. + ++-------+------------+----------+----------------------------+ +| GINO | SQLAlchemy | Dialect | Comments | ++=======+============+==========+============================+ +| 1.0.x | 1.3.x | Custom | Current (old-)stable. | ++-------+------------+----------+----------------------------+ +| 1.1.x | 1.3.x | Custom | Next old-stable. | ++-------+------------+----------+----------------------------+ +| 1.2.x | 1.3.x | Custom | Future old-stable (maybe). | ++-------+------------+----------+----------------------------+ +| 1.4.x | 1.4.x | Upstream | 2.0 Interim. | ++-------+------------+----------+----------------------------+ +| 2.0.x | 2.0.x | Upstream | Future stable. | ++-------+------------+----------+----------------------------+ +| 2.1.x | 2.0.x | Upstream | Future stable iterations. | ++-------+------------+----------+----------------------------+ + +To make things easier, GINO will (luckily) also `follow the same versions +`__ for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only. + +At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won't match SQLAlchemy versions. + + +The Async Solution +------------------ + +Among all the exciting updates in SQLAlchemy 1.4 / 2.0, `native async support +`__ is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet_ to mix asynchronous stuff into current code base, avoiding making everything +async. + +Let's say we have an asynchronous method to create an asyncpg connection:: + + import asyncpg + + async def connect(): + return await asyncpg.connect("postgresql:///") + +And an end-user method to use it:: + + async def main(): + conn = await connect() + now = await conn.fetchval("SELECT now()") + +Now instead of directly calling ``connect()`` from ``main()``, I would like to add some +additional logic - let's say, a sanity check:: + + async def safe_connect(): + conn = await connect() + try: + await conn.execute("SELECT 1") + except Exception: + return None + else: + return conn + +Then the end-user should modify ``main()`` to: + +.. code-block:: python + :emphasize-lines: 2,3 + + async def main(): + conn = await safe_connect() + if conn: + now = await conn.fetchval("SELECT now()") + +OK, everything works so far, as they are all regular async code. Here's the interesting +part: ``safe_connect()`` must not be an ``async def`` method. With SQLAlchemy 1.4+, we +could: + +.. code-block:: python + :emphasize-lines: 1,3,4,6,13 + + from sqlalchemy.util import await_only, greenlet_spawn + + def sync_safe_connect(): + conn = await_only(connect()) + try: + await_only(conn.execute("SELECT 1")) + except Exception: + return None + else: + return conn + + async def safe_connect(): + return await greenlet_spawn(sync_safe_connect) + +Behind the scene, ``greenlet_spawn()`` runs the given "sync" method in a greenlet, which +uses ``await_only()`` to switch to the event loop and bridge the underlying async +methods. As ``sync_safe_connect()`` is just a normal Python method, you can imagine how +it works together with lots of other "sync" code asynchronously. + +We're not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its "sync" code base, and still being able to provide async +APIs on top of them. + + +Async SQLAlchemy +---------------- + +Although greenlet_ might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move. + +The sync library existed for years, with many assumptions like using ``threading.Lock`` +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. ``asyncio.Lock``. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues. + +As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, ``threading.Lock.acquire()`` actually works fine in a single coroutine, but `2 +concurrent coroutines `__ +acquiring the same ``threading.Lock`` may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread. + +Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base. + +However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that's GINO's part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won't work +because there is no place for ``await`` in accessing attributes (GINO doesn't like such +implicitness anyways, so yeah). + +To quickly get a picture of async SQLAlchemy (Core), here's a sample from SQLAlchemy:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + + async with engine.begin() as conn: + await conn.run_sync(meta.drop_all) + await conn.run_sync(meta.create_all) + + await conn.execute( + t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}] + ) + + async with engine.connect() as conn: + + # select a Result, which will be delivered with buffered + # results + result = await conn.execute(select(t1).where(t1.c.name == "some name 1")) + + print(result.fetchall()) + + + asyncio.run(async_main()) + + +Auto-Commit Complication +------------------------ + +After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that ``BEGIN`` starts a transaction and +``COMMIT`` / ``ROLLBACK`` ends it. But what is happening to SQL statements that is not +wrapped in ``BEGIN ... COMMIT`` blocks? + + If you do not issue a ``BEGIN`` command, then each individual statement has an + implicit ``BEGIN`` and (if successful) ``COMMIT`` wrapped around it. + + -- PostgreSQL Documentation, `3.4. Transactions + `__ + +And yes, implicit ``ROLLBACK`` if not successful. This is not directly named as an +"auto-commit" feature, but PostgreSQL does enforce it. Now imagine a database whose +"auto-commit" feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed. + +`PEP 249 `__ (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only ``commit()`` and ``rollback()`` on a +connection, but no ``begin()``. So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call ``commit()`` to persist your changes. Closing a connection will +cause pending transactions rolled back automatically. + + Note that if the database supports an auto-commit feature, this (*the auto-commit + feature -- GINO comments*) must be initially off. + + -- PEP 249 + +As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +"auto-commit" has to mimic such behavior. For example, psycopg2_ will automatically emit +a ``BEGIN`` to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen ``IDLE IN TRANSACTION``?), sometimes even +holding database locks and eventually causing a deadlock storm. + +To work around this workaround, PEP 249 does say: + + An interface method may be provided to turn it (the auto-commit feature) back on. + +So for psycopg2_, one could do this:: + + import psycopg2 + + conn = psycopg2.connect("postgresql:///") + conn.autocommit = True + conn.cursor().execute("SELECT now()") + +Now the database correctly receives this ``SELECT`` statement only, without any implicit +``BEGIN`` surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:: + + conn.cursor().execute("BEGIN") + conn.cursor().execute("UPDATE ...") + conn.cursor().execute("COMMIT") + +Or 2) turn auto-commit off again:: + + conn.autocommit = False + conn.cursor().execute("UPDATE ...") + conn.commit() + +I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg_ does provide a cleaner API, by not complying to PEP 249:: + + import asyncpg + + async def main(): + conn = await asyncpg.connect("postgresql://") + + print(await conn.fetchval("SELECT now()")) # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.execute("UPDATE ...") # UPDATE ...; + # COMMIT; + +It's much cleaner to see what's actually happening on the wire to the database. This is +also how GINO works. + + +SQLAlchemy for DB-API +--------------------- + +Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:: + + import sqlalchemy as sa + + e = sa.create_engine("postgresql:///", future=True) + with e.connect() as conn: + conn.scalar(sa.text("SELECT now()")) + +Only ``SELECT now()``? No. Here's the answer:: + + with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + conn.scalar(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +.. note:: + + We are using SQLAlchemy 2.0 API for simplification, by setting ``future=True`` using + SQLAlchemy 1.4. It's way more complicated in SQLAlchemy 1.3 and I don't want to get + into that. + +The reason behind this is, connections have "auto-commit" turned off by default. If +there is no transaction, an implicit ``BEGIN`` will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg_ +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg_ and +simulated a compatible DB-API. Like this:: + + import sqlalchemy as sa + from sqlalchemy.ext.asyncio import create_async_engine + + async def main(): + e = create_async_engine("postgresql+asyncpg:///") + async with e.connect() as conn: # BEGIN; SELECT version(); ...; ROLLBACK; + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +If you want to modify the database permanently, you have to ``commit()`` the implicit +transaction explicitly: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("UPDATE ...")) # BEGIN; UPDATE ...; + await conn.commit() # COMMIT; + +Or use the explicit transaction API: + +.. code-block:: python + :emphasize-lines: 4,6 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Please note that, SQLAlchemy 2.0 doesn't allow soft-nested transactions. In other words, +you cannot nest 2 ``async with conn.begin():`` blocks like this: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + async with conn.begin(): # BEGIN; + async with conn.begin(): # Error: a transaction is already begun + ... + +This limitation applies to implicit transactions too, even though it's weird: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + await conn.rollback() # ROLLBACK; + async with conn.begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +Similar to Core, SQLAlchemy ORM `follows the same principal +`__. Grab a session, use +it without ``begin()``, and when you want to commit, ``commit()``. Or, use an explicit +transaction in a ``with session.begin():`` block. Personally I don't like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more. + + +SQLAlchemy AUTOCOMMIT +--------------------- + +I know you already miss the WYSIWYG asyncpg_ and GINO API. Hang in there, let's build +GINO 1.4 together with the `SQLAlchemy AUTOCOMMIT feature +`__. + +To turn AUTOCOMMIT back on, we need to set the ``isolation_level`` to ``AUTOCOMMIT`` in +``execution_options``: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Hooray! No more implicit ``BEGIN`` magic. We're one step closer. + +.. note:: + + There is also a keyword argument: + + .. code-block:: python + :emphasize-lines: 3 + + e = create_async_engine( + "postgresql+asyncpg:///", + isolation_level="AUTOCOMMIT", + ) + + But this is implemented very differently than ``execution_options``, and I don't + think it's working for GINO's use case. + +The next question is, how do we explicitly start a transaction? Let's try ``begin()``: + +.. code-block:: python + :emphasize-lines: 5 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + async with conn.begin(): # Error: a transaction is already begun + ... + +Wait a second ... we've seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we're in AUTOCOMMIT mode? Even +though the ``isolation_level`` tell the driver not to send ``BEGIN`` to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction: + +.. code-block:: python + :emphasize-lines: 5,6 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.begin(): # no-op + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + +Well, not quite what we expected. With AUTOCOMMIT set, all of ``begin()``, ``commit()`` +and ``rollback()`` become no-ops. + +Similar to the answers in psycopg2_, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let's try 2): + +.. code-block:: python + :emphasize-lines: 6-8 + + async def main(): + e = ... + async with e.connect() as conn: + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + await conn.rollback() # no-op + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + +It's working! According to SQLAlchemy docs, ``execution_options()`` creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well... + +.. code-block:: python + :emphasize-lines: 11,12 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + await conn.execute(sa.text("SELECT now()")) # BEGIN; SELECT now(); + # ROLLBACK; + +Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same "DB-API" connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting ``isolation_level`` +modifies the value on "DB-API" connection. + +Returning a SQLAlchemy connection back to the pool resets the ``isolation_level`` to its +default value, and acquiring the same connection again will initialize the +``isolation_level`` with values from ``execution_options`` of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +``isolation_level`` again: + +.. code-block:: python + :emphasize-lines: 11 + + async def main(): + e = ... + async with e.connect() as conn: + ... + async with conn.execution_options( + isolation_level="READ COMMITTED" + ).begin(): # BEGIN; + await conn.execute(sa.text("UPDATE..")) # UPDATE ...; + # COMMIT; + + conn.execution_options(isolation_level="AUTOCOMMIT") + await conn.execute(sa.text("SELECT now()")) # SELECT now(); + +Eventually we made it! 🎉 + +Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:: + + import gino + + async def main(): + engine = await gino.create_engine("postgresql:///") + async with engine.acquire() as conn: + await conn.scalar("SELECT now()") # SELECT now(); + + async with conn.transaction(): # BEGIN; + await conn.status("UPDATE ...") # UPDATE ...; + # COMMIT; + +.. hint:: + + Now I feel that "implementing" auto-commit feature is more like restoring to the + original database behavior, and having auto-commit turned off by default should be + considered as a new feature called "auto-begin" or "implicit transaction". And it's + a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem. + + +Isolation Levels +---------------- + +By far, we only used 2 ``isolation_level`` values: + +* ``AUTOCOMMIT`` +* ``READ COMMITTED`` + +``AUTOCOMMIT`` is not a valid PostgreSQL isolation level. It's only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the "auto-begin" simulation. + +``READ COMMITTED`` is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction: + +.. code-block:: plpgsql + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +To start a transaction in a different isolation level, you may: + +.. code-block:: plpgsql + :emphasize-lines: 1,7 + + # BEGIN ISOLATION LEVEL SERIALIZABLE; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + +As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit ``BEGIN`` in place, an implicit transaction is used. So this SQL also works +individually: + +.. code-block:: plpgsql + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + +But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels: + +.. code-block:: plpgsql + :emphasize-lines: 1,7,10,16,22,28 + + # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE; + SET + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # BEGIN; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + serializable + (1 row) + + # ROLLBACK; + ROLLBACK + + # BEGIN ISOLATION LEVEL READ COMMITTED; + BEGIN + + # SHOW TRANSACTION ISOLATION LEVEL; + transaction_isolation + ----------------------- + read committed + (1 row) + + # ROLLBACK; + ROLLBACK + +Then let's see how SQLAlchemy with asyncpg solves this problem: + +.. code-block:: python + :emphasize-lines: 4,7 + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "SERIALIZABLE"}, + ) + async with e.connect() as conn: + async with conn.begin(): # BEGIN ISOLATION LEVEL SERIALIZABLE; + await conn.execute(sa.text("UPDATE ...")) # UPDATE ...; + # COMMIT; + +Under the neath, SQLAlchemy is leveraging asyncpg's +``Connection.transaction(isolation="...")`` to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions. + +But there are 2 issues: + +* User-defined isolation level is not applied in PostgreSQL implicit transactions + (a.k.a. auto-commit statements), because no one ``SET SESSION``. +* asyncpg has a bug that ``Connection.transaction(isolation="read_committed")`` always + emit ``BEGIN`` without explicit isolation level, regardless of the actual default + isolation level. + +The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL: + +.. code-block:: python + :emphasize-lines: 13-20,23,24 + + import sqlalchemy as sa + from sqlalchemy import event + from sqlalchemy.dialects.postgresql.base import PGDialect + from sqlalchemy.ext.asyncio import create_async_engine + + + async def main(): + e = create_async_engine( + "postgresql+asyncpg:///", + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + + def set_isolation_level(dbapi_conn, record): + PGDialect.set_isolation_level( + e.sync_engine.dialect, + dbapi_conn, + "SERIALIZABLE", + ) + + event.listen(e.sync_engine, "connect", set_isolation_level) + + async with e.connect() as conn: + print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL"))) + # Outputs: serializable + + +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ +.. _psycopg2: https://www.psycopg.org/docs/ +.. _asyncpg: https://github.com/MagicStack/asyncpg diff --git a/docs/zh/master/_sources/explanation/why.rst.txt b/docs/zh/master/_sources/explanation/why.rst.txt new file mode 100644 index 0000000..77455b5 --- /dev/null +++ b/docs/zh/master/_sources/explanation/why.rst.txt @@ -0,0 +1,244 @@ +Why Asynchronous ORM? +===================== + +Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly. + + +When asyncio Helps +------------------ + +As Mike Bayer - the author of SQLAlchemy - `pointed out +`__, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM". + +The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +:doc:`async`. So the ultimate reason to use asyncio should be the application itself, +not the database. + +For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead. + +.. image:: ../images/263px-Minimum-Tonne.svg.png + :align: right + +Another example is authentication using `OpenID Connect Authorization Code Flow +`__ as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +`Liebig's law `__, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave. + +Arbitrary delays may happen in the database too. In PostgreSQL, you can |LISTEN|_ to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case. + +In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is: + +.. |LISTEN| replace:: ``LISTEN`` +.. _LISTEN: https://www.postgresql.org/docs/current/sql-listen.html + + +How to Access Database +---------------------- + +The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here. + +Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:: + + import asyncio + from fastapi import FastAPI + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + app = FastAPI() + e = create_engine("postgresql://localhost") + Session = sessionmaker(bind=e) + + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +This follows advices found in :ref:`session_faq_whentocreate` from SQLAlchemy - linking +the **session scope** to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is ``kill -9``. + +What just happened is called a `resource starvation +`__. While the first 10 +requests were waiting for the async work (``sleep(0.1)``), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default ``max_overflow=10``) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition. + +The root cause of such starvation is making asynchronous calls in a **transaction +scope** created implicitly by the default ``autocommit=False``. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in **transaction scopes** by explicitly ending them:: + + @app.get("/") + async def read_root(): + session = Session() + try: + now = session.execute("SELECT now()").scalar() + session.rollback() # return the connection to avoid starvation + await asyncio.sleep(0.1) # do some async work + return str(now) + finally: + session.close() + +Because of the implicit nature of typical ORMs, it's very hard to identify all such +**transaction scopes** and make sure there's no ``await`` in them - you may have started +an implicit transaction by simply accessing a property like ``current_user.name``. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this. + +What about thread pool? + +The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool. + +This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery_. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design. + +In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs. + +.. _Celery: http://www.celeryproject.org/ + + +Asynchronous ORM Done Right +--------------------------- + +I would describe a proper asynchronous ORM as follows: + + +Don't Starve. +^^^^^^^^^^^^^ + +The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything ``async``. + +In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:: + + @app.get("/") + async def read_root(): + async with db.acquire() as conn: # } + async with conn.begin(): # } These two won't block the main thread + now = await db.scalar("SELECT now()") + await asyncio.sleep(0.1) # do some async work + return str(now) + +Even though this code won't cause any resource starvation, using ``await`` within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages: + +1. As the database connection pool is usually much smaller than the asynchronous server + concurrency, this kind of code caps the concurrency down to the DB pool level. +2. Taking a database connection from the pool for nothing is a waste of resource, + especially when the database pool is the shortest stave. +3. Database transactions should be kept short as much as possible. Because long-hanging + transactions may keep certain database locks, leading to performance issues or even + triggering a chain reaction of deadlocks. + + +Be explicit. +^^^^^^^^^^^^ + +With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an ``await`` or ``async with``, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following `the pattern of Twisted +`__, asyncio is already forcing +explicit ``await`` for any asynchronous operations. + +Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example: + +* We could design the ORM model instances to be stateless, so that the users don't have + to learn and worry about maintaining the state of the instances. +* There shouldn't be any "buffered operations" which users could "flush" with a single + statement once for all. +* The user should just give direct one-off commands and the ORM executes them right away. +* Also I think the convenience tooling should be well-balanced, the user doesn't have to + guess or remember what an API means - for example, there're more than one ways to load + a many-to-one relationship, I'd prefer to write the query by myself rather than trying + to remember what "join_without_n_plus_1()" means. + + +Be productive. +^^^^^^^^^^^^^^ + +Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two? + +I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design. + +Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again. diff --git a/docs/zh/master/_sources/how-to.rst.txt b/docs/zh/master/_sources/how-to.rst.txt new file mode 100644 index 0000000..00a13df --- /dev/null +++ b/docs/zh/master/_sources/how-to.rst.txt @@ -0,0 +1,7 @@ +How-to Guides +============= + +.. toctree:: + :glob: + + how-to/* diff --git a/docs/zh/master/_sources/how-to/alembic.rst.txt b/docs/zh/master/_sources/how-to/alembic.rst.txt new file mode 100644 index 0000000..aa60714 --- /dev/null +++ b/docs/zh/master/_sources/how-to/alembic.rst.txt @@ -0,0 +1,135 @@ +Use Alembic +=========== + +Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO. + +To add migrations to project first of all, add alembic as dependency: + +.. code-block:: console + + $ pip install --user alembic + +When you need to set up alembic for your project. + +Prepare sample project. We will have a structure: + + +.. code-block:: console + + alembic_sample/ + my_app/ + models.py + +Inside ``models.py`` define simple DB Model with GINO: + +.. code-block:: python + + from gino import Gino + + db = Gino() + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + +Set up Alembic +^^^^^^^^^^^^^^ + +This will need to be done only once. Go to the main folder of your project +``alembic_sample`` and run: + +.. code-block:: console + + $ alembic init alembic + + +Alembic will create a bunch of files and folders in your project directory. One of them +will be ``alembic.ini``. Open ``alembic.ini`` (you can find it in the main project +folder ``alembic_sample``). Now change property ``sqlalchemy.url =`` with your DB +credentials. Like this: + +.. code-block:: ini + + sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}} + + +Next go to folder ``alembic/`` and open ``env.py`` file. Inside the ``env.py`` file you +need to import the ``db`` object. In our case ``db`` object is ``db`` from ``models`` +modules. This is a variable that links to your ``Gino()`` instance. + +Inside ``alembic/env.py``:: + + from main_app.models import db + + +And change ``target_metadata =`` to:: + + target_metadata = db + +That’s it. We finished setting up Alembic for a project. + +.. note:: + + All ``alembic`` commands must be run always from the folder that contains the + ``alembic.ini`` file. + + +Create first migration revision +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema. + +.. code-block:: console + + $ alembic revision -m "first migration" --autogenerate --head head + +If you have any problems relative to package imports similar to this example: + +.. code-block:: console + + File "alembic/env.py", line 7, in + from main_app.models import db + ModuleNotFoundError: No module named 'main_app' + +Either install your project locally with ``pip install -e .``, ``poetry install`` or +``python setup.py develop``, or add you package to PYTHONPATH, like this: + +.. code-block:: console + + $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample + +After the successful run of ``alembic revision`` in folder ``alembic/versions`` you will +see a file with new migration. + + +Apply migration on DB +^^^^^^^^^^^^^^^^^^^^^ + +Now time to apply migration to DB. It will create tables based on you DB Models. + +.. code-block:: console + + $ alembic upgrade head + +Great. Now you apply your first migration. Congratulations! + +Next time, when you will make any changes in DB models just do: + +.. code-block:: console + + $ alembic revision -m "your migration description" --autogenerate --head head + +And + +.. code-block:: console + + alembic upgrade head + + +Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org diff --git a/docs/zh/master/_sources/how-to/bakery.rst.txt b/docs/zh/master/_sources/how-to/bakery.rst.txt new file mode 100644 index 0000000..2f30075 --- /dev/null +++ b/docs/zh/master/_sources/how-to/bakery.rst.txt @@ -0,0 +1,214 @@ +Bake Queries +============ + +.. versionadded:: 1.1 + +Baked queries are used to boost execution performance for constantly-used queries. +Similar to the :doc:`orm/extensions/baked` in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to **bake them before creating the engine**. + +GINO provides two approaches for baked queries: + +1. Low-level :class:`~gino.bakery.Bakery` API +2. High-level :meth:`Gino.bake() ` integration + + +Use Bakery with Bare Engine +--------------------------- + +First, we need a bakery:: + + import gino + + bakery = gino.Bakery() + +Then, let's bake some queries:: + + db_time = bakery.bake("SELECT now()") + +Or queries with parameters:: + + user_query = bakery.bake("SELECT * FROM users WHERE id = :uid") + +Let's assume we have this ``users`` table defined in SQLAlchemy Core:: + + import sqlalchemy as sa + + metadata = sa.MetaData() + user_table = sa.Table( + "users", metadata, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("name", sa.String), + ) + +Now we can bake a similar query with SQLAlchemy Core:: + + user_query = bakery.bake( + sa.select([user_table]).where(user.c.id == sa.bindparam("uid")) + ) + +These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:: + + engine = await gino.create_engine("postgresql://localhost/", bakery=bakery) + +By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene. + +To execute the baked queries, you could treat the :class:`~gino.bakery.BakedQuery` +instances as if they are the queries themselves, for example:: + + now = await engine.scalar(db_time) + +Pass in parameter values:: + + row = await engine.first(user_query, uid=123) + + +Use the :class:`~gino.api.Gino` Integration +-------------------------------------------- + +In a more common scenario, there will be a :class:`~gino.api.Gino` instance, which has +usually a ``bind`` set - either explicitly or by the Web framework extensions:: + + from gino import Gino + + db = Gino() + + async def main(): + async with db.with_bind("postgresql://localhost/"): + ... + +A :class:`~gino.bakery.Bakery` is automatically created in the ``db`` instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + db_time = db.bake("SELECT now()") + user_getter = db.bake(User.query.where(User.id == db.bindparam("uid"))) + +And the execution is also simplified with the same ``bind`` magic:: + + async def main(): + async with db.with_bind("postgresql://localhost/"): + print(await db_time.scalar()) + + user: User = await user_getter.first(uid=1) + print(user.name) + +To make things easier, you could even define the baked queries directly on the +model:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + + @db.bake + def getter(cls): + return cls.query.where(cls.id == db.bindparam("uid")) + + @classmethod + async def get(cls, uid): + return await cls.getter.one_or_none(uid=uid) + +Here GINO treats the ``getter()`` as a :meth:`~gino.declarative.declared_attr` with +``with_table=True``, therefore it takes one positional argument ``cls`` for the ``User`` +class. + + +How to customize loaders? +------------------------- + +If possible, you could bake the additional execution options into the query:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + ) + +The :meth:`~gino.bakery.Bakery.bake` method accepts keyword arguments as execution +options to e.g. simplify the example above into:: + + user_getter = db.bake( + User.query.where(User.id == db.bindparam("uid")), + loader=User.load(comment="Added by loader."), + ) + +If the query construction is complex, :meth:`~gino.bakery.Bakery.bake` could also be +used as a decorator:: + + @db.bake + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")).execution_options( + loader=User.load(comment="Added by loader.") + ) + +Or with short execution options:: + + @db.bake(loader=User.load(comment="Added by loader.")) + def user_getter(): + return User.query.where(User.id == db.bindparam("uid")) + +Meanwhile, it is also possible to override the loader at runtime:: + + user: User = await user_getter.load(User).first(uid=1) + print(user.name) # no more comment on user! + +.. hint:: + + This override won't affect the baked query - it's used only in this execution. + + +What APIs are available on :class:`~gino.bakery.BakedQuery`? +------------------------------------------------------------ + +:class:`~gino.bakery.BakedQuery` is a :class:`~gino.api.GinoExecutor`, so it inherited +all the APIs like :meth:`~gino.api.GinoExecutor.all`, +:meth:`~gino.api.GinoExecutor.first`, :meth:`~gino.api.GinoExecutor.one`, +:meth:`~gino.api.GinoExecutor.one_or_none`, :meth:`~gino.api.GinoExecutor.scalar`, +:meth:`~gino.api.GinoExecutor.status`, :meth:`~gino.api.GinoExecutor.load`, +:meth:`~gino.api.GinoExecutor.timeout`, etc. + +:class:`~gino.api.GinoExecutor` is actually the chained ``.gino`` helper API seen +usually in queries like this:: + + user = await User.query.where(User.id == 123).gino.first() + +So a :class:`~gino.bakery.BakedQuery` can be seen as a normal query with the ``.gino`` +suffix, plus it is directly executable. + +.. seealso:: + + Please see API document of :mod:`gino.bakery` for more information. + + +I don't want the prepared statements. +------------------------------------- + +If you don't need all the baked queries (``m``) to create prepared statements for all +the active database connections (``n``) in the beginning, you could set +``prebake=False`` in the engine initialization to prevent the default initial +``m x n`` prepare calls:: + + e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False) + +Or if you're using bind:: + + await db.set_bind("postgresql://...", prebake=False) + +This is useful when you're depending on ``db.gino.create_all()`` to create the tables, +because the prepared statements can only be created after the table creation. + +The prepared statements will then be created and cached lazily on demand. + diff --git a/docs/zh/master/_sources/how-to/contributing.rst.txt b/docs/zh/master/_sources/how-to/contributing.rst.txt new file mode 100644 index 0000000..e3924e2 --- /dev/null +++ b/docs/zh/master/_sources/how-to/contributing.rst.txt @@ -0,0 +1,152 @@ +.. highlight:: console + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/python-gino/gino/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +GINO could always use more documentation, whether as part of the +official GINO docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `gino` for local development. + +1. Fork the `gino` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/gino.git + +3. Create a branch for local development:: + + $ cd gino/ + $ git checkout -b name-of-your-bugfix-or-feature + +Now you can make your changes locally. + +4. Create virtual environment. Example for virtualenvwrapper:: + + $ mkvirtualenv gino + +5. Activate the environment and install requirements:: + + $ pip install -r requirements_dev.txt + +6. When you're done making changes, check that your changes pass syntax checks:: + + $ flake8 gino tests + +7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):: + + $ pytest tests + $ tox + +8. For docs run:: + + $ make docs + +It will build and open up docs in your browser. + +9. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +10. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 3.6, 3.7, 3.8 and 3.9. Check + https://github.com/python-gino/gino/actions?query=event%3Apull_request + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test -svx tests.test_gino + +By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:: + + CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino'; + CREATE DATABASE gino WITH OWNER = gino; + +Then run the tests like so:: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino + $ py.test + +Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):: + + $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem + $ openssl rsa -in privkey.pem -passin pass:abcd -out server.key + $ openssl req -x509 -in server.req -text -key server.key -out server.crt + $ chmod 600 server.key + $ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + +Terminal 2 (client):: + + $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433 + $ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'" + $ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;" + $ pytest tests/test_aiohttp.py diff --git a/docs/zh/master/_sources/how-to/crud.rst.txt b/docs/zh/master/_sources/how-to/crud.rst.txt new file mode 100644 index 0000000..dd23336 --- /dev/null +++ b/docs/zh/master/_sources/how-to/crud.rst.txt @@ -0,0 +1,5 @@ +==== +CRUD +==== + +**THIS IS A WIP** diff --git a/docs/zh/master/_sources/how-to/extensions.rst.txt b/docs/zh/master/_sources/how-to/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/zh/master/_sources/how-to/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/zh/master/_sources/how-to/extensions/sanic.rst.txt b/docs/zh/master/_sources/how-to/extensions/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/zh/master/_sources/how-to/extensions/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/zh/master/_sources/how-to/extensions/starlette.rst.txt b/docs/zh/master/_sources/how-to/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/zh/master/_sources/how-to/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/zh/master/_sources/how-to/extensions/tornado.rst.txt b/docs/zh/master/_sources/how-to/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/zh/master/_sources/how-to/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/zh/master/_sources/how-to/faq.rst.txt b/docs/zh/master/_sources/how-to/faq.rst.txt new file mode 100644 index 0000000..06f1298 --- /dev/null +++ b/docs/zh/master/_sources/how-to/faq.rst.txt @@ -0,0 +1,389 @@ +Frequently Asked Questions +========================== + +SQLAlchemy 1.4 supports asyncio, what will GINO be? +--------------------------------------------------- + +Starting from 1.4, SQLAlchemy will `support asyncio +`__. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +`greenlet `__. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed. + +Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features: + +* Contextual Connections (see :doc:`../explanation/engine`) +* SQLAlchemy Core-based CRUD models +* The GINO Loader system +* Async MySQL support +* Typing support :sup:`NEW` +* `Trio `__ support :sup:`NEW` +* Execution performance :sup:`NEW` + +As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized. + + +ORM or not ORM? +--------------- + +GINO does perform the Object-Relational Mapping work under the +`Data Mapper Pattern `_, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely `non-ORM way +`__. + + +Can I use features of SQLAlchemy ORM? +------------------------------------- + +SQLAlchemy has `two parts `__: + +* SQLAlchemy Core +* SQLAlchemy ORM + +GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO. + + +How to join? +------------ + +GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know `how to write join in +SQLAlchemy `_. +Especially, `Ádám `_ made some amazing upgrades in +GINO `#113 `_ to make join easier, so +that you can use model classes directly as if they are tables in joining:: + + results = await User.join(Book).select().gino.all() + + +How to connect to database through SSL? +--------------------------------------- + +It depends on the dialect and database driver. For asyncpg, keyword arguments +on :func:`asyncpg.connect() ` are directly +available on :func:`~gino.create_engine` or :meth:`db.set_bind() +`. Therefore, enabling SSL is rather easy:: + + engine = await gino.create_engine(..., ssl=True) + + +What is aiocontextvars and what does it do? +------------------------------------------- + +It is a partial backport of the new built-in module `contextvars +`_ introduced in Python +3.7. In Python 3.6, ``aiocontextvars`` patches ``loop.create_task()`` +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2 + +If you are using Python 3.7, then ``aiocontextvars`` does nothing at all. + +.. note:: + + This answer is for GINO 0.8 and later, please check earlier versions of + this documentation if you are using GINO 0.7. + + +How to define relationships? +---------------------------- + +GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see :doc:`loaders` for more information. + + +How to define index with multiple columns? +------------------------------------------ + +:: + + class User(db.Model): + __tablename__ = 'users' + + first_name = db.Column(db.Unicode()) + last_name = db.Column(db.Unicode()) + + _name_idx = db.Index('index_on_name', 'first_name', 'last_name') + +The ``_name_idx`` is not used. + + +Is there a django admin interface for GINO? +------------------------------------------- + +Not quite yet, please follow `this discussion +`__. + + +How to use multiple databases for different users on the fly? +------------------------------------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. + +In order to use multiple databases, you would need multiple +:class:`~gino.engine.GinoEngine` instances. Here's a full example using FastAPI with +lazy engine creation:: + + from asyncio import Future + from contextvars import ContextVar + + from fastapi import FastAPI, Request + from gino import create_engine + from gino.ext.starlette import Gino + + engines = {} + dbname = ContextVar("dbname") + + + class ContextualGino(Gino): + @property + def bind(self): + e = engines.get(dbname.get("")) + if e and e.done(): + return e.result() + else: + return self._bind + + @bind.setter + def bind(self, val): + self._bind = val + + + app = FastAPI() + db = ContextualGino(app) + + + @app.middleware("http") + async def lazy_engines(request: Request, call_next): + name = request.query_params.get("db", "postgres") + fut = engines.get(name) + if fut is None: + fut = engines[name] = Future() + try: + engine = await create_engine("postgresql://localhost/" + name) + except Exception as e: + fut.set_exception(e) + del engines[name] + raise + else: + fut.set_result(engine) + await fut + dbname.set(name) + return await call_next(request) + + + @app.get("/") + async def get(): + return dict(dbname=await db.scalar("SELECT current_database()")) + + +How to load complex query results? +---------------------------------- + +The API doc of :mod:`gino.loader` explains the available loaders, and there're a few +examples in :doc:`loaders` too. + +Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a :class:`~gino.loader.TupleLoader` with two sub-loaders - +:class:`~gino.loader.ModelLoader` and :class:`~gino.loader.ColumnLoader`:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Be ware of the :class:`tuple` in ``.gino.load((...))``. + + + +How to do bulk or batch insert / update? +----------------------------------------- + +For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:: + + new_names = ["Austin", "Ali", "Jeff", "Marissa"] + +To quickly insert the names in one query, first construct a dict with the +``{"model_key": "value"}`` format:: + + new_names_dict = [dict(name=new_name) for new_name in new_names] + >> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}] + +Finally, run an insert statement on the model:: + + await User.insert().gino.all(new_names_dict) + + +How to print the executed SQL? +------------------------------ + +GINO uses the same approach from SQLAlchemy: ``create_engine(..., echo=True)``. +(Or ``db.set_bind(..., echo=True)``) Please see also `here +`__. + +If you use any extension, you can also set that in config, by `db_echo` or `DB_ECHO`. + + +How to run ``EXISTS`` SQL? +-------------------------- + +:: + + await db.scalar(db.exists().where(User.email == email).select()) + + +How to work with Alembic? +------------------------- + +The fact that :class:`~gino.api.Gino` is a :class:`~sqlalchemy.schema.MetaData` is the +key to use Alembic. Just import and set ``target_metadata = db`` in Alembic ``env.py`` +will do. See :doc:`alembic` for more details. + + +How to join the same table twice? +--------------------------------- + +This is the same pattern as described in SQLAlchemy :ref:`self_referential`, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +:func:`~gino.crud.CRUDModel.alias`, for example:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.ForeignKey("users.id")) + + Parent = User.alias() + query = User.outerjoin(Parent, User.parent_id == Parent.id).select() + users = await query.gino.load(User.load(parent=Parent)).all() + + +.. _raw-sql: + +How to execute raw SQL with parameters? +--------------------------------------- + +Wrap the SQL with :func:`~sqlalchemy.sql.expression.text`, and use keyword arguments:: + + query = db.text('SELECT * FROM users WHERE id = :id_val') + row = await db.first(query, id_val=1) + +You may even load the rows into model instances:: + + query = query.execution_options(loader=User) + user = await db.first(query, id_val=1) + + +Gino engine is not initialized? +------------------------------- + +GINO models are linked to a :class:`~gino.api.Gino` instance, while +:class:`~gino.api.Gino` has an optional property ``bind`` to hold a +:class:`~gino.engine.GinoEngine` instance. So when you are executing:: + + user = await User.get(request.user_id) + +The ``bind`` is implicitly used to execute the query. If ``bind`` is not set before +this, you'll see this error: + +.. code-block:: text + + gino.exceptions.UninitializedError: Gino engine is not initialized. + +You could use either: + +* Call :meth:`~gino.api.Gino.set_bind` or :meth:`~gino.api.Gino.with_bind` to set the + bind on the :class:`~gino.api.Gino` instance. +* Use one of the Web framework extensions to set the bind for you in usually the server + start-up hook. +* Use explicit ``bind`` for each execution, for example:: + + engine = await create_engine("...") + # ... + user = await User.get(request.user_id, bind=engine) + + +How can I do SQL xxxx in GINO? +------------------------------ + +GINO uses `SQLAlchemy Core `__ queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy :class:`~sqlalchemy.schema.Table` instances, and the column +attributes on GINO model classes are just SQLAlchemy :class:`~sqlalchemy.schema.Column` +instances, you can use them in building your SQLAlchemy Core queries. + +Alternatively, you could always execute the raw SQL directly, see :ref:`raw-sql` above. diff --git a/docs/zh/master/_sources/how-to/json-props.rst.txt b/docs/zh/master/_sources/how-to/json-props.rst.txt new file mode 100644 index 0000000..45cf95d --- /dev/null +++ b/docs/zh/master/_sources/how-to/json-props.rst.txt @@ -0,0 +1,157 @@ +JSON Property +============= + +GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields. + +Quick Start +----------- + +:: + + from gino import Gino + from sqlalchemy.dialects.postgresql import JSONB + + db = Gino() + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + birthday = db.DateTimeProperty() + +The ``age`` and ``birthday`` are JSON properties stored in the ``profile`` column. You +may use them the same way as a normal GINO model field:: + + u = await User.create(name="daisy", age=18) + print(u.name, u.age) # daisy 18 + +.. note:: + + ``profile`` is the default column name for all JSON properties in a model. If you + need a different column name for some JSON properties, you'll need to specify + explicitly:: + + audit_profile = db.Column(JSON, nullable=False, server_default="{}") + + access_log = db.ArrayProperty(prop_name="audit_profile") + abnormal_detected = db.BooleanProperty(prop_name="audit_profile") + +Using JSON properties in queries is supported:: + + await User.query.where(User.age > 16).gino.all() + +This is simply translated into a native JSON query like this: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS INTEGER) > $2; -- ('age', 16) + +Datetime type is very much the same:: + + from datetime import datetime + + await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all() + +And the generated SQL: + +.. code-block:: plpgsql + + SELECT users.id, users.name, users.profile + FROM users + WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2 + -- ('birthday', datetime.datetime(1990, 1, 1, 0, 0)) + +Here's a list of all the supported JSON properties: + ++----------------------------+-----------------------------+-------------+---------------+ +| JSON Property | Python type | JSON type | Database Type | ++============================+=============================+=============+===============+ +| :class:`.StringProperty` | :class:`str` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.IntegerProperty` | :class:`int` | ``number`` | ``int`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.BooleanProperty` | :class:`bool` | ``boolean`` | ``boolean`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.DateTimeProperty` | :class:`~datetime.datetime` | ``string`` | ``text`` | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ObjectProperty` | :class:`dict` | ``object`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ +| :class:`.ArrayProperty` | :class:`list` | ``array`` | JSON | ++----------------------------+-----------------------------+-------------+---------------+ + + +Hooks +----- + +JSON property provides 2 instance-level hooks to customize the data:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @age.before_set + def age(self, val): + return val - 1 + + @age.after_get + def age(self, val): + return val + 1 + + u = await User.create(name="daisy", age=18) + print(u.name, u.profile, u.age) # daisy {'age': 17} 18 + +And 1 class-level hook to customize the SQLAlchemy expression of the property:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + height = db.JSONProperty() + + @height.expression + def height(cls, exp): + return exp.cast(db.Float) # CAST(profile -> 'height' AS FLOAT) + + +Create Index on JSON Properties +------------------------------- + +We'll need to use :meth:`~gino.declarative.declared_attr` to wait until the model class +is initialized. The rest is very much the same as defining a usual index:: + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + profile = db.Column(JSONB, nullable=False, server_default="{}") + + age = db.IntegerProperty() + + @db.declared_attr + def age_idx(cls): + return db.Index("age_idx", cls.age) + +This will lead to the SQL below executed if you run ``db.gino.create_all()``: + +.. code-block:: plpgsql + + CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER)); + +.. warning:: + + Alembic doesn't support auto-generating revisions for functional indexes yet. You'll + need to manually edit the revision. Please follow `this issue + `__ for updates. diff --git a/docs/zh/master/_sources/how-to/loaders.rst.txt b/docs/zh/master/_sources/how-to/loaders.rst.txt new file mode 100644 index 0000000..6d82937 --- /dev/null +++ b/docs/zh/master/_sources/how-to/loaders.rst.txt @@ -0,0 +1,461 @@ +======================== +Loaders and Relationship +======================== + +Loaders are used to load database row results into objects. + +GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined. + + +Model Loader +------------ + +The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:: + + query = db.select([User]) + rows = await query.gino.all() + +In order to load rows into ``User`` objects, you can provide an execution +option ``loader`` with a new :class:`~gino.loader.ModelLoader` instance:: + + from gino.loader import ModelLoader + + query = db.select([User]) + query = query.execution_options(loader=ModelLoader(User)) + users = await query.gino.all() + +The :class:`~gino.loader.ModelLoader` would then load each database row into a +``User`` object. As this is frequently used, GINO made it a shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User.load()) + users = await query.gino.all() + +And another shortcut:: + + query = db.select([User]) + query = query.execution_options(loader=User) + users = await query.gino.all() + +.. tip:: + + ``User`` as ``loader`` is transformed into ``ModelLoader(User)`` by + :meth:`Loader.get() `, explained later in "Loader + Expression". + +And again:: + + query = db.select([User]) + users = await query.gino.load(User).all() + +This is identical to the normal CRUD query:: + + users = await User.query.gino.all() + + +Loader Expression +----------------- + +So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than :class:`~gino.loader.ModelLoader`, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders. + +Here is an example using all loaders at once:: + + uid, user, sep, cols = await db.select([User]).gino.load( + ( + User.id, + User, + '|', + lambda row, ctx: len(row), + ) + ).first() + +Let's check this piece by piece. Overall, the argument of +:meth:`~gino.api.GinoExecutor.load` is a tuple. This is interpreted into a +:class:`~gino.loader.TupleLoader`, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a :class:`~gino.loader.TupleLoader` is a tuple. + +:class:`~sqlalchemy.schema.Column` in Loader Expressions are interpreted as +:class:`~gino.loader.ColumnLoader`. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, :class:`~gino.loader.ColumnLoader` uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use :class:`~gino.loader.ColumnLoader`, +you'll have to declare columns for the query:: + + now = db.Column('time', db.DateTime()) + result = await db.first(db.text( + 'SELECT now() AT TIME ZONE \'UTC\'' + ).columns( + now, + ).gino.load( + ('now:', now) + ).query) + print(*result) # now: 2018-04-08 08:23:02.431847 + +Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +:class:`~gino.loader.ModelLoader`. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values. + +.. tip:: + + For a complex loader expression, the same row is given to all loaders, so + it doesn't matter ``User.id`` is already used before the model loader. + +The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +:func:`map`, the return value of the call will be the loaded result. + +At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the ``'|'`` separator, it will show up as the third item +in every result returned by the query. + + +Many-to-One Relationship +------------------------ + +A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + +So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:: + + async for child in Child.load(parent=Parent).gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +As you may have noticed, ``Child.load`` is exactly the shortcut to create +:class:`~gino.loader.ModelLoader` in the very first example. With some +additional keyword arguments, ``Child.load(parent=Parent)`` is still a +:class:`~gino.loader.ModelLoader` for ``Child``, the model loader is at the +same time a **query builder**. It is identical to do this:: + + async for child in Child.load(parent=Parent).query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +The :attr:`~gino.loader.Loader.query` dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The :class:`~gino.loader.Loader` simply forwarded unknown +attributes to its :attr:`~gino.loader.Loader.query`, that's why ``.query`` can +be omitted. + +For :class:`~gino.loader.ModelLoader`, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using :func:`setattr`. For example, ``Parent`` is +interpreted as ``ModelLoader(Parent)`` which loads ``Parent`` instances, and +``Parent`` instances are set as the ``parent`` attribute of the outer ``Child`` +instance. + +.. warning:: + + If multiple children references the same parent, then each child owns a + unique parent instance with identical values. + +.. tip:: + + You don't have to define ``parent`` attribute on ``Child``. But if you do, + you gain the ability to customize how parent is stored or retrieved. For + example, let's store the parent instance as ``_parent``:: + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + _parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + +The query builder works recursively. For :class:`~gino.loader.ModelLoader`, it +uses ``LEFT OUTER JOIN`` to connect the ``FROM`` clauses, in order to achieve +many-to-one scenario. The ``ON`` clause is determined automatically by foreign +keys. You can also customize the ``ON`` clause in case there is no foreign key +(a promise is a promise):: + + loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + async for child in loader.query.gino.iterate(): + print(f'Parent of {child.id} is {child.parent.id}') + +And subloaders can be nested:: + + subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id)) + loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id)) + +By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values. + + +Self Referencing +---------------- + +.. warning:: + + Experimental feature. + +Self referencing is usually used to create a tree-like structure. For example:: + + class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('categories.id')) + +In order to load leaf categories with their parents, an alias is needed:: + + Parent = Category.alias() + +Then the query would be something like this:: + + parents = db.select([Category.parent_id]) + query = Category.load(parent=Parent.on( + Category.parent_id == Parent.id + )).where( + ~Category.id.in_(db.select([Category.alias().parent_id])) + ) + async for c in query.gino.iterate(): + print(f'Leaf: {c.id}, Parent: {c.parent.id}') + +The generated SQL looks like this: + +.. code-block:: SQL + + SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id + FROM categories LEFT OUTER JOIN categories AS categories_1 + ON categories.parent_id = categories_1.id + WHERE categories.id NOT IN ( + SELECT categories_2.parent_id + FROM categories AS categories_2 + ) + + +Other Relationships +------------------- + +GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + @children.setter + def add_child(self, child): + self._children.add(child) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child)).all() + +Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the ``add_child`` setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +``parent.add_child = new_child``. + +Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries. + +GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._child = None + + @property + def child(self): + return self._child + + @child.setter + def child(self, child): + self._child = child + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + + + query = Child.outerjoin(Parent).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all() + + +Similarly, you can build many-to-many relationships in the same way:: + + class Parent(db.Model): + __tablename__ = 'parents' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._children = set() + + @property + def children(self): + return self._children + + def add_child(self, child): + self._children.add(child) + child._parents.add(self) + + + class Child(db.Model): + __tablename__ = 'children' + id = db.Column(db.Integer, primary_key=True) + + def __init__(self, **kw): + super().__init__(**kw) + self._parents = set() + + @property + def parents(self): + return self._parents + + + class ParentXChild(db.Model): + __tablename__ = 'parents_x_children' + + parent_id = db.Column(db.Integer, db.ForeignKey('parents.id')) + child_id = db.Column(db.Integer, db.ForeignKey('children.id')) + + + query = Parent.outerjoin(ParentXChild).outerjoin(Child).select() + parents = await query.gino.load( + Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all() + +Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ``ParentXChild`` instances. + + +Advanced Usage of Loaders +------------------------- + +You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For `example +`_, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:: + + import asyncio + import random + import string + + import gino + from gino.loader import ColumnLoader + + db = gino.Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.Unicode()) + + + class Visit(db.Model): + __tablename__ = 'visits' + + id = db.Column(db.Integer(), primary_key=True) + time = db.Column(db.DateTime(), server_default='now()') + user_id = db.Column(db.ForeignKey('users.id')) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + + for i in range(random.randint(5, 10)): + u = await User.create( + name=''.join(random.choices(string.ascii_letters, k=10))) + for v in range(random.randint(10, 20)): + await Visit.create(user_id=u.id) + + visits = db.func.count(Visit.id) + q = db.select([ + User, + visits, + ]).select_from( + User.outerjoin(Visit) + ).group_by( + *User, + ).gino.load((User, ColumnLoader(visits))) + async with db.transaction(): + async for user, visits in q.iterate(): + print(user.name, visits) + + await db.gino.drop_all() + + + asyncio.run(main()) + +Using alias to get ID-ascending pairs from the same table:: + + ua1 = User.alias() + ua2 = User.alias() + join_query = select([ua1, ua2]).where(ua1.id < ua2.id) + loader = ua1.load('id'), ua2.load('id') + result = await join_query.gino.load(loader).all() + print(result) # e.g. [(1, 2), (1, 3), (2, 3)] + +Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future. diff --git a/docs/zh/master/_sources/how-to/pool.rst.txt b/docs/zh/master/_sources/how-to/pool.rst.txt new file mode 100644 index 0000000..4574ab1 --- /dev/null +++ b/docs/zh/master/_sources/how-to/pool.rst.txt @@ -0,0 +1,27 @@ +=============== +Connection Pool +=============== + +Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +:class:`~gino.dialects.asyncpg.NullPool`), and users can define their own pools. +The base class should be :class:`~gino.dialects.base.Pool`. + +To use non-default pools in raw GINO:: + + from gino.dialects.asyncpg import NullPool + create_engine('postgresql://...', pool_class=NullPool) + +To use non-default pools in extensions (taking Sanic as an example):: + + from gino.dialects.asyncpg import NullPool + from gino.ext.sanic import Gino + + app = sanic.Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_KWARGS = dict( + pool_class=NullPool, + ) + db = Gino() + db.init_app(app) diff --git a/docs/zh/master/_sources/how-to/sanic.rst.txt b/docs/zh/master/_sources/how-to/sanic.rst.txt new file mode 100644 index 0000000..1de33f0 --- /dev/null +++ b/docs/zh/master/_sources/how-to/sanic.rst.txt @@ -0,0 +1,141 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/zh/master/_sources/how-to/schema.rst.txt b/docs/zh/master/_sources/how-to/schema.rst.txt new file mode 100644 index 0000000..c57eaaa --- /dev/null +++ b/docs/zh/master/_sources/how-to/schema.rst.txt @@ -0,0 +1,232 @@ +================== +Schema Declaration +================== + +There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy :class:`~sqlalchemy.schema.Table`. + + +GINO Engine +----------- + +This is the minimized way to use GINO - using only +:class:`~gino.engine.GinoEngine` (and :class:`~gino.engine.GinoConnection` +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two. + +For example, the table declaration is the same as SQLAlchemy core `tutorial +`_:: + + from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey + + metadata = MetaData() + + users = Table( + 'users', metadata, + + Column('id', Integer, primary_key=True), + Column('name', String), + Column('fullname', String), + ) + + addresses = Table( + 'addresses', metadata, + + Column('id', Integer, primary_key=True), + Column('user_id', None, ForeignKey('users.id')), + Column('email_address', String, nullable=False) + ) + +.. note:: + + When using GINO Engine only, it is usually your own business to create the + tables with either :meth:`~sqlalchemy.schema.MetaData.create_all` on a + normal non-async SQLAlchemy engine, or using Alembic. However it is still + possible to be done with GINO if it had to:: + + import gino + from gino.schema import GinoSchemaVisitor + + async def main(): + engine = await gino.create_engine('postgresql://...') + await GinoSchemaVisitor(metadata).create_all(engine) + +Then, construct queries, in SQLAlchemy core too:: + + ins = users.insert().values(name='jack', fullname='Jack Jones') + +So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:: + + async def main(): + engine = await gino.create_engine('postgresql://localhost/gino') + conn = await engine.acquire() + await conn.status(ins) + print(await conn.all(users.select())) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Here :func:`~gino.create_engine` creates a :class:`~gino.engine.GinoEngine`, +then :meth:`~gino.engine.GinoEngine.acquire` checks out a +:class:`~gino.engine.GinoConnection`, and +:meth:`~gino.engine.GinoConnection.status` executes the insert and returns the +status text. This works similarly as SQLAlchemy +:meth:`~sqlalchemy.engine.Connection.execute` - they take the same parameters +but return a bit differently. There are also other similar query APIs: + +* :meth:`~gino.engine.GinoConnection.all` returns a list of + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.first` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.one` returns one + :class:`~sqlalchemy.engine.RowProxy` +* :meth:`~gino.engine.GinoConnection.one_or_none` returns one + :class:`~sqlalchemy.engine.RowProxy`, or ``None`` +* :meth:`~gino.engine.GinoConnection.scalar` returns a single value, or + ``None`` +* :meth:`~gino.engine.GinoConnection.iterate` returns an asynchronous iterator + which yields :class:`~sqlalchemy.engine.RowProxy` + +Please go to their API for more information. + + +GINO Core +--------- + +In previous scenario, :class:`~gino.engine.GinoEngine` must not be set to +:attr:`metadata.bind ` because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of :class:`~sqlalchemy.schema.MetaData` as :class:`~gino.api.Gino`, +usually instantiated globally under the name of ``db``. It can be used as a +normal :class:`~sqlalchemy.schema.MetaData` still offering some conveniences: + +* It delegates most public types you can access on ``sqlalchemy`` +* It works with both normal SQLAlchemy engine and asynchronous GINO engine +* It exposes all query APIs on :class:`~gino.engine.GinoConnection` level +* It injects two ``gino`` extensions on SQLAlchemy query clauses and schema + items, allowing short inline execution like ``users.select().gino.all()`` +* It is also the entry for the third scenario, see later + +Then we can achieve previous scenario with less code like this:: + + from gino import Gino + + db = Gino() + + users = db.Table( + 'users', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('name', db.String), + db.Column('fullname', db.String), + ) + + addresses = db.Table( + 'addresses', db, + + db.Column('id', db.Integer, primary_key=True), + db.Column('user_id', None, db.ForeignKey('users.id')), + db.Column('email_address', db.String, nullable=False) + ) + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await users.insert().values( + name='jack', + fullname='Jack Jones', + ).gino.status() + print(await users.select().gino.all()) + # Outputs: [(1, 'jack', 'Jack Jones')] + +Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but ``sqlalchemy`` seems +never imported. This is useful when ORM is unwanted. + +.. tip:: + + `asyncpgsa `_ does the same thing, + but in a conceptually reversed way - instead of having asyncpg work for + SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that + way too because GINO is inspired by asyncpgsa). Either way works fine, it's + just a matter of taste of whose API style to use, SQLAlchemy or asyncpg. + + +GINO ORM +-------- + +If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:: + + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + fullname = db.Column(db.String) + + + class Address(db.Model): + __tablename__ = 'addresses' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(None, db.ForeignKey('users.id')) + email_address = db.Column(db.String, nullable=False) + + + async def main(): + async with db.with_bind('postgresql://localhost/gino'): + await db.gino.create_all() + await User.create(name='jack', fullname='Jack Jones') + print(await User.query.gino.all()) + # Outputs: [] + +.. important:: + + The ``__tablename__`` is a mandatory field to define a concrete model. + +As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in ``db``. The ``class`` style is just +more declarative. Instead of ``users.c.name``, you can now access the column by +``User.name``. The implicitly created :class:`~sqlalchemy.schema.Table` is +available at ``User.__table__`` and ``Address.__table__``. You can use anything +that works in GINO core here. + +.. note:: + + Column names can be different as a class property and database column. + For example, name can be declared as + ``nickname = db.Column('name', db.Unicode(), default='noname')``. In this + example, ``User.nickname`` is used to access the column, while in database, + the column name is ``name``. + + What's worth mentioning is where raw SQL statements are used, or + ``TableClause`` is involved, like ``User.insert()``, the original name is + required to be used, because in this case, GINO has no knowledge about the + mappings. + +.. tip:: + + ``db.Model`` is a dynamically created parent class for your models. It is + associated with the ``db`` on initialization, therefore the table is put in + the very ``db`` when you declare your model class. + +Things become different when it comes to CRUD. You can use model level methods +to directly :meth:`~gino.crud.CRUDModel.create` a model instance, instead of +inserting a new row. Or :meth:`~gino.crud.CRUDModel.delete` a model instance +without needing to specify the where clause manually. Query returns model +instances instead of :class:`~sqlalchemy.engine.RowProxy`, and row values are +directly available as attributes on model instances. See also: +:doc:`/how-to/crud`. + +After all, :class:`~gino.engine.GinoEngine` is always in use. Next let's dig +more into it. diff --git a/docs/zh/master/_sources/how-to/starlette.rst.txt b/docs/zh/master/_sources/how-to/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/zh/master/_sources/how-to/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/zh/master/_sources/how-to/tornado.rst.txt b/docs/zh/master/_sources/how-to/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/zh/master/_sources/how-to/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/zh/master/_sources/how-to/transaction.rst.txt b/docs/zh/master/_sources/how-to/transaction.rst.txt new file mode 100644 index 0000000..558a5ae --- /dev/null +++ b/docs/zh/master/_sources/how-to/transaction.rst.txt @@ -0,0 +1,116 @@ +=========== +Transaction +=========== + +It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an ``await`` will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it. + + +Basic usage +----------- + +Transactions belong to :class:`~gino.engine.GinoConnection`. The most common +way to use transactions is through an ``async with`` statement:: + + async with connection.transaction() as tx: + await connection.status('INSERT INTO mytable VALUES(1, 2, 3)') + +This guarantees a transaction is opened when entering the ``async with`` block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at :attr:`~gino.transaction.GinoTransaction.raw_transaction`, but in +most cases you don't need to touch it. + +GINO provides two convenient shortcuts to end the transaction early: + +* :meth:`tx.raise_commit() ` +* :meth:`tx.raise_rollback() ` + +They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the ``async with`` block after +:meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` is skipped. The +internal exception is inherited from :exc:`BaseException` so that normal ``try +... except Exception`` block can't trap it. This exception stops propagating at +the end of ``async with`` block, so you don't need to worry about handling it. + +Transactions can also be started on a :class:`~gino.engine.GinoEngine`:: + + async with engine.transaction() as tx: + await engine.status('INSERT INTO mytable VALUES(1, 2, 3)') + +Here a :class:`~gino.engine.GinoConnection` is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The :class:`~gino.engine.GinoConnection` instance is accessible at +:attr:`tx.connection `. Other than +that, everything else is the same. + +.. important:: + + The implicit connection is by default borrowed with ``reuse=True``. That + means using :meth:`~gino.engine.GinoEngine.transaction` of + :class:`~gino.engine.GinoEngine` within a connection context is the same as + calling :meth:`~gino.engine.GinoConnection.transaction` of the current + connection without having to reference it, no separate connection shall be + created. + +Similarly, if your :class:`~gino.api.Gino` instance has a bind, you may also do +the same on it:: + + async with db.transaction() as tx: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + + +Nested Transactions +------------------- + +Transactions can be nested, nested transaction will create a `savepoint +`_ as for +now on asyncpg. A similar example from asyncpg doc:: + + async with connection.transaction() as tx1: + await connection.status('CREATE TABLE mytab (a int)') + + # Create a nested transaction: + async with connection.transaction() as tx2: + await connection.status('INSERT INTO mytab (a) VALUES (1), (2)') + # Rollback the nested transaction: + tx2.raise_rollback() + + # Because the nested transaction was rolled back, there + # will be nothing in `mytab`. + assert await connection.all('SELECT a FROM mytab') == [] + +As you can see, the :meth:`~gino.transaction.GinoTransaction.raise_rollback` +breaks only the ``async with`` block of the specified ``tx2``, the outer +transaction ``tx1`` just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate. + +Because of the default reusing behavior, transactions on engine or ``db`` +follows the same nesting rules. Please see +:class:`~gino.transactions.GinoTransaction` for more information. + + +Manual Control +-------------- + +Other than using ``async with``, you can also manually control the +transaction:: + + tx = await connection.transaction() + try: + await db.status('INSERT INTO mytable VALUES(1, 2, 3)') + await tx.commit() + except Exception: + await tx.rollback() + raise + +You can't use :meth:`~gino.transaction.GinoTransaction.raise_commit` or +:meth:`~gino.transaction.GinoTransaction.raise_rollback` here, similarly it is +prohibited to use :meth:`~gino.transaction.GinoTransaction.commit` and +:meth:`~gino.transaction.GinoTransaction.rollback` in an ``async with`` block. diff --git a/docs/zh/master/_sources/index.rst.txt b/docs/zh/master/_sources/index.rst.txt new file mode 100644 index 0000000..1512377 --- /dev/null +++ b/docs/zh/master/_sources/index.rst.txt @@ -0,0 +1,127 @@ +Welcome to GINO's documentation! +================================ + +.. image:: https://img.shields.io/pypi/v/gino?logo=python&logoColor=white&color=3E6CDE&style=flat-square + :alt: PyPI Release Version + :target: https://pypi.python.org/pypi/gino + +.. image:: https://img.shields.io/github/workflow/status/python-gino/gino/test?label=test&logo=github&color=3E6CDE&style=flat-square + :alt: GitHub Workflow Status for tests + :target: https://github.com/python-gino/gino/actions?query=workflow%3Atest + +.. image:: https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy&color=3E6CDE&style=flat-square + :alt: Codacy coverage + :target: https://app.codacy.com/gh/python-gino/gino/dashboard + +.. image:: https://img.shields.io/badge/Dependabot-active-brightgreen?logo=dependabot&color=3E6CDE&style=flat-square + :target: https://app.dependabot.com/accounts/python-gino/projects/129260 + :alt: Dependabot + + +GINO - GINO Is Not ORM - is a lightweight asynchronous ORM built on top of +SQLAlchemy_ core for Python asyncio_. Now (early 2020) GINO supports only one +dialect asyncpg_. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _asyncpg: https://github.com/MagicStack/asyncpg + + +.. cssclass:: boxed-nav + +* .. image:: images/tutorials.svg + + :doc:`tutorials` + + Lessons for the newcomer to get started + +* .. image:: images/how-to.svg + + :doc:`how-to` + + Solve specific problems by steps + +* .. image:: images/explanation.svg + + :doc:`explanation` + + Explains the background and context + +* .. image:: images/reference.svg + + :doc:`reference` + + Describes the software as it is + + +Useful Links +------------ + +.. cssclass:: boxed-nav + +* .. image:: images/github.svg + + `Source Code `_ + + https://github.com/python-gino/gino + +* .. image:: images/community.svg + + `Community `_ + + https://gitter.im/python-gino/Lobby + +* .. image:: images/open-source.svg + + `BSD license `_ + + GINO is free software + +* .. image:: images/python.svg + + `Download `_ + + Download GINO from PyPI + + +.. cssclass:: divio + +Sections by `Divio `_. + +.. toctree:: + :caption: Tutorials + :maxdepth: 1 + :glob: + :hidden: + + tutorials + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* + +.. toctree:: + :caption: How-to Guides + :maxdepth: 1 + :glob: + :hidden: + + how-to + how-to/* + +.. toctree:: + :caption: Explanation + :maxdepth: 1 + :glob: + :hidden: + + explanation + explanation/* + +.. toctree:: + :caption: Reference + :maxdepth: 1 + :glob: + :hidden: + + reference + reference/* diff --git a/docs/zh/master/_sources/reference.rst.txt b/docs/zh/master/_sources/reference.rst.txt new file mode 100644 index 0000000..977912b --- /dev/null +++ b/docs/zh/master/_sources/reference.rst.txt @@ -0,0 +1,7 @@ +Reference +========= + +.. toctree:: + :glob: + + reference/* diff --git a/docs/zh/master/_sources/reference/api.rst.txt b/docs/zh/master/_sources/reference/api.rst.txt new file mode 100644 index 0000000..23fc548 --- /dev/null +++ b/docs/zh/master/_sources/reference/api.rst.txt @@ -0,0 +1,6 @@ +API Reference +============= + +.. toctree:: + + api/gino diff --git a/docs/zh/master/_sources/reference/api/gino.aiocontextvars.rst.txt b/docs/zh/master/_sources/reference/api/gino.aiocontextvars.rst.txt new file mode 100644 index 0000000..4728456 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.aiocontextvars.rst.txt @@ -0,0 +1,7 @@ +gino.aiocontextvars module +========================== + +.. automodule:: gino.aiocontextvars + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.api.rst.txt b/docs/zh/master/_sources/reference/api/gino.api.rst.txt new file mode 100644 index 0000000..8825a74 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.api.rst.txt @@ -0,0 +1,7 @@ +gino.api module +=============== + +.. automodule:: gino.api + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.bakery.rst.txt b/docs/zh/master/_sources/reference/api/gino.bakery.rst.txt new file mode 100644 index 0000000..5762ab7 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.bakery.rst.txt @@ -0,0 +1,7 @@ +gino.bakery module +================== + +.. automodule:: gino.bakery + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.crud.rst.txt b/docs/zh/master/_sources/reference/api/gino.crud.rst.txt new file mode 100644 index 0000000..9683c99 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.crud.rst.txt @@ -0,0 +1,7 @@ +gino.crud module +================ + +.. automodule:: gino.crud + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.declarative.rst.txt b/docs/zh/master/_sources/reference/api/gino.declarative.rst.txt new file mode 100644 index 0000000..8ec0bdd --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.declarative.rst.txt @@ -0,0 +1,7 @@ +gino.declarative module +======================= + +.. automodule:: gino.declarative + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.dialects.aiomysql.rst.txt b/docs/zh/master/_sources/reference/api/gino.dialects.aiomysql.rst.txt new file mode 100644 index 0000000..338ccc5 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.dialects.aiomysql.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.aiomysql module +============================= + +.. automodule:: gino.dialects.aiomysql + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.dialects.asyncpg.rst.txt b/docs/zh/master/_sources/reference/api/gino.dialects.asyncpg.rst.txt new file mode 100644 index 0000000..9423016 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.dialects.asyncpg.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.asyncpg module +============================ + +.. automodule:: gino.dialects.asyncpg + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.dialects.base.rst.txt b/docs/zh/master/_sources/reference/api/gino.dialects.base.rst.txt new file mode 100644 index 0000000..c512657 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.dialects.base.rst.txt @@ -0,0 +1,7 @@ +gino.dialects.base module +========================= + +.. automodule:: gino.dialects.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.dialects.rst.txt b/docs/zh/master/_sources/reference/api/gino.dialects.rst.txt new file mode 100644 index 0000000..5366ba2 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.dialects.rst.txt @@ -0,0 +1,20 @@ +gino.dialects package +===================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects.aiomysql + gino.dialects.asyncpg + gino.dialects.base + +Module contents +--------------- + +.. automodule:: gino.dialects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.engine.rst.txt b/docs/zh/master/_sources/reference/api/gino.engine.rst.txt new file mode 100644 index 0000000..0075c29 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.engine.rst.txt @@ -0,0 +1,7 @@ +gino.engine module +================== + +.. automodule:: gino.engine + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.exceptions.rst.txt b/docs/zh/master/_sources/reference/api/gino.exceptions.rst.txt new file mode 100644 index 0000000..d3659da --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.exceptions.rst.txt @@ -0,0 +1,7 @@ +gino.exceptions module +====================== + +.. automodule:: gino.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.ext.rst.txt b/docs/zh/master/_sources/reference/api/gino.ext.rst.txt new file mode 100644 index 0000000..fe56088 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.ext.rst.txt @@ -0,0 +1,10 @@ +gino.ext package +================ + +Module contents +--------------- + +.. automodule:: gino.ext + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.json_support.rst.txt b/docs/zh/master/_sources/reference/api/gino.json_support.rst.txt new file mode 100644 index 0000000..5a7c069 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.json_support.rst.txt @@ -0,0 +1,7 @@ +gino.json\_support module +========================= + +.. automodule:: gino.json_support + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.loader.rst.txt b/docs/zh/master/_sources/reference/api/gino.loader.rst.txt new file mode 100644 index 0000000..06ce463 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.loader.rst.txt @@ -0,0 +1,7 @@ +gino.loader module +================== + +.. automodule:: gino.loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.rst.txt b/docs/zh/master/_sources/reference/api/gino.rst.txt new file mode 100644 index 0000000..b507d15 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.rst.txt @@ -0,0 +1,38 @@ +gino package +============ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + gino.dialects + gino.ext + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + gino.aiocontextvars + gino.api + gino.bakery + gino.crud + gino.declarative + gino.engine + gino.exceptions + gino.json_support + gino.loader + gino.schema + gino.strategies + gino.transaction + +Module contents +--------------- + +.. automodule:: gino + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.schema.rst.txt b/docs/zh/master/_sources/reference/api/gino.schema.rst.txt new file mode 100644 index 0000000..36fbb62 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.schema.rst.txt @@ -0,0 +1,7 @@ +gino.schema module +================== + +.. automodule:: gino.schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.strategies.rst.txt b/docs/zh/master/_sources/reference/api/gino.strategies.rst.txt new file mode 100644 index 0000000..01de55b --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.strategies.rst.txt @@ -0,0 +1,7 @@ +gino.strategies module +====================== + +.. automodule:: gino.strategies + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/api/gino.transaction.rst.txt b/docs/zh/master/_sources/reference/api/gino.transaction.rst.txt new file mode 100644 index 0000000..2a28e55 --- /dev/null +++ b/docs/zh/master/_sources/reference/api/gino.transaction.rst.txt @@ -0,0 +1,7 @@ +gino.transaction module +======================= + +.. automodule:: gino.transaction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/zh/master/_sources/reference/authors.rst.txt b/docs/zh/master/_sources/reference/authors.rst.txt new file mode 100644 index 0000000..1266f7f --- /dev/null +++ b/docs/zh/master/_sources/reference/authors.rst.txt @@ -0,0 +1,49 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Fantix King + +Maintainers +----------- + +* Tony Wang + +Contributors +------------ + +* Neal Wang +* Binghan Li +* Vladimir Goncharov +* Kinware +* Kentoseth +* Ádám Barancsuk +* Sergey Kovalev +* jonahfang +* Yurii Shtrikker +* Nicolas Crocfer +* Denys Badzo +* Pavol Vargovcik +* Mykyta Holubakha +* Jekel +* Martin Zaťko +* Pascal van Kooten +* Michał Dziewulski +* Simeon J Morgan +* Julio Lacerda +* qulaz +* Jim O'Brien +* Ilaï Deutel +* Roald Storm +* Tiago Requeijo +* Olexiy + + +Special thanks to my wife Daisy and her outsourcing company `DecentFoX Studio`_, +for offering me the opportunity to build this project. We are open for global +software project outsourcing on Python, iOS and Android development. + +.. _DecentFoX Studio: https://decentfox.com/ diff --git a/docs/zh/master/_sources/reference/extensions.rst.txt b/docs/zh/master/_sources/reference/extensions.rst.txt new file mode 100644 index 0000000..660c240 --- /dev/null +++ b/docs/zh/master/_sources/reference/extensions.rst.txt @@ -0,0 +1,7 @@ +Extensions +========== + +.. toctree:: + :glob: + + extensions/* diff --git a/docs/zh/master/_sources/reference/extensions/sanic.rst.txt b/docs/zh/master/_sources/reference/extensions/sanic.rst.txt new file mode 100644 index 0000000..5a910b4 --- /dev/null +++ b/docs/zh/master/_sources/reference/extensions/sanic.rst.txt @@ -0,0 +1,143 @@ +============= +Sanic Support +============= + +**THIS IS A WIP** + + +Work with Sanic +--------------- + +Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default. + +The lazy connection is actually established if necessary, i.e. just before first access to db. + +This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default. + +Supported configurations: + +- DB_HOST +- DB_PORT +- DB_USER +- DB_PASSWORD +- DB_DATABASE +- DB_ECHO +- DB_POOL_MIN_SIZE +- DB_POOL_MAX_SIZE +- DB_SSL +- DB_USE_CONNECTION_FOR_REQUEST +- DB_KWARGS + +An example server: + +:: + + from sanic import Sanic + from sanic.exceptions import abort + from sanic.response import json + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_DATABASE = 'gino' + db = Gino() + db.init_app(app) + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode()) + + def __repr__(self): + return '{}<{}>'.format(self.nickname, self.id) + + + @app.route("/users/") + async def get_user(request, user_id): + if not user_id.isdigit(): + abort(400, 'invalid user id') + user = await User.get_or_404(int(user_id)) + return json({'name': user.nickname}) + + + if __name__ == '__main__': + app.run(debug=True) + + +Sanic Support +------------- + +To integrate with Sanic, a few configurations needs to be set in +``app.config`` (with default value though): + +- DB_HOST: if not set, ``localhost`` +- DB_PORT: if not set, ``5432`` +- DB_USER: if not set, ``postgres`` +- DB_PASSWORD: if not set, empty string +- DB_DATABASE: if not set, ``postgres`` +- DB_ECHO: if not set, ``False`` +- DB_POOL_MIN_SIZE: if not set, 5 +- DB_POOL_MAX_SIZE: if not set, 10 +- DB_SSL: if not set, ``None`` +- DB_KWARGS; if not set, empty dictionary + +An example: + +.. code-block:: python + + from sanic import Sanic + from gino.ext.sanic import Gino + + app = Sanic() + app.config.DB_HOST = 'localhost' + app.config.DB_USER = 'postgres' + + db = Gino() + db.init_app(app) + + +After ``db.init_app``, a connection pool with configured settings shall be +created and bound to ``db`` when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. ``User.get(1)``, everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response. + +Please be noted that, in the async world, ``await`` may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this: + +.. code-block:: python + + await request['connection'].release() + + +Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting ``reuse=False``: + +.. code-block:: python + + async with db.acquire(reuse=False): + # new connection context is created + + +Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off: + +.. code-block:: python + + app.config.DB_USE_CONNECTION_FOR_REQUEST = False + + diff --git a/docs/zh/master/_sources/reference/extensions/starlette.rst.txt b/docs/zh/master/_sources/reference/extensions/starlette.rst.txt new file mode 100644 index 0000000..a7b76d7 --- /dev/null +++ b/docs/zh/master/_sources/reference/extensions/starlette.rst.txt @@ -0,0 +1,86 @@ +================= +Starlette Support +================= + +Work with Starlette +------------------- + +To use GINO with Starlette, the `gino-starlette +`_ package should +be installed first: + +.. code-block:: console + + pip install gino-starlette + +.. note:: + The gino-starlette package supports only GINO 1.0 or later. + Earlier versions of GINO like 0.8.x have built-in Starlette support. + +This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the ``kwargs`` +parameter. + +The common usage looks like this: + +.. code-block:: python + + from starlette.applications import Starlette + from gino.ext.starlette import Gino + + app = Starlette() + db = Gino(app, **kwargs) + + # Or with application factory + app = Starlette() + db = Gino(**kwargs) + db.init_app(app) + +Configuration +------------- + +The config includes: + ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| Name | Description | Default | ++==================================+=======================================================================================================================+=================+ +| ``driver`` | the database driver | ``asyncpg`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``host`` | database server host | ``localhost`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``port`` | database server port | ``5432`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``user`` | database server user | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``password`` | database server password | empty | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``database`` | database name | ``postgres`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``dsn`` | a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_min_size`` | the initial number of connections of the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``pool_max_size`` | the maximum number of connections in the db pool. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``echo`` | enable SQLAlchemy echo mode. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``ssl`` | SSL context passed to ``asyncpg.connect`` | ``None`` | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``use_connection_for_request`` | flag to set up lazy connection for requests. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ +| ``kwargs`` | other parameters passed to the specified dialects, like ``asyncpg``. Unrecognized parameters will cause exceptions. | N/A | ++----------------------------------+-----------------------------------------------------------------------------------------------------------------------+-----------------+ + +Lazy Connection +--------------- + +If ``use_connection_for_request`` is set to be True, then a lazy +connection is available at ``request['connection']``. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this: + +.. code:: python + + await request['connection'].release(permanent=False) diff --git a/docs/zh/master/_sources/reference/extensions/tornado.rst.txt b/docs/zh/master/_sources/reference/extensions/tornado.rst.txt new file mode 100644 index 0000000..c69ef5b --- /dev/null +++ b/docs/zh/master/_sources/reference/extensions/tornado.rst.txt @@ -0,0 +1,5 @@ +=============== +Tornado Support +=============== + +**THIS IS A WIP** diff --git a/docs/zh/master/_sources/reference/history.rst.txt b/docs/zh/master/_sources/reference/history.rst.txt new file mode 100644 index 0000000..e10bb4e --- /dev/null +++ b/docs/zh/master/_sources/reference/history.rst.txt @@ -0,0 +1,624 @@ +======= +History +======= + +GINO 1.1 +-------- + +1.1.0 (pending) +^^^^^^^^^^^^^^^ + +* Added baked query feature (#478 #659 #667) +* Added ``Query.gino.execution_options`` shortcut (#659) +* Added ``@db.declared_attr(with_table=True)`` (#659) +* [Breaking] Empty object instead of ``None`` being returned for objects with values of all selected columns are None (#729) +* Added MySQL support (#381 #685) +* [Breaking] asyncpg is no longer installed as a dependency by default, install ``gino[pg]`` for the old behavior +* Fixed multiple referenced connection stack in newly created coroutines (#747) + + +GINO 1.0 +-------- + +Migrating to GINO 1.0 +^^^^^^^^^^^^^^^^^^^^^ + +GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras: + ++------------------------+---------------------------------+ +| Extension Module | Installation in GINO 1.0 | ++========================+=================================+ +| ``gino.ext.starlette`` | ``pip install gino[starlette]`` | ++------------------------+---------------------------------+ +| ``gino.ext.aiohttp`` | ``pip install gino[aiohttp]`` | ++------------------------+---------------------------------+ +| ``gino.ext.sanic`` | ``pip install gino[sanic]`` | ++------------------------+---------------------------------+ +| ``gino.ext.tornado`` | ``pip install gino[tornado]`` | ++------------------------+---------------------------------+ +| ``gino.ext.quart`` | ``pip install gino[quart]`` | ++------------------------+---------------------------------+ + +The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed ``gino[starlette]``:: + + from gino.ext.starlette import Gino + +GINO 1.0 switched to `Poetry `__ for package and +dependency management and started to use the +`src layout `__. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process: + +* Source files are now located under ``src`` directory. +* The src dist on PyPI does not include tests, docs and some other files due to + a limitation of Poetry. + +1.0.1 (2020-06-08) +^^^^^^^^^^^^^^^^^^ + +* Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4) +* Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672) +* Multiple JSON property fixes (#661 #662 #695) +* Fixed extension typing issue (#673 #674) +* Fixed model override behavior (#694) +* Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696) + +1.0.0 (2020-04-26) +^^^^^^^^^^^^^^^^^^ + +* Switched to Poetry for package and dependency management. +* [Breaking] Moved built-in extension modules to separate PyPI packages. +* Switched to src layout. +* Switched to black code style. +* Better documentation. +* [Breaking] ``none_as_none()`` is now always enabled. +* Added representation method for engine. +* Protected the URL instance fed to ``set_bind()`` from manipulation. +* Replaced some ``assert`` with ``AssertionError`` (#258 #655) + + +GINO 0.8 +-------- + +This is also version 1.0 release candidate. + +Migrating to GINO 0.8 +^^^^^^^^^^^^^^^^^^^^^ + +1. contextvars +"""""""""""""" + +We introduced aiocontextvars_ 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the ``enable_inherit()`` or +``disable_inherit()`` calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created **after** importing ``gino`` or ``aiocontextvars``, or the patch +won't work correctly. + +There is nothing to worry about in Python 3.7. + +2. none_as_none +""""""""""""""" + +When GINO tries to load a row with all ``NULL`` values into an instance, it +will now by default return ``None`` instead of an instance with all ``None`` +attributes. To recover the default behavior of 0.7, please specify +``none_as_none(False)`` in affected model loader. + +This is especially applicable to relationship sub-loaders - if the sub-loader +found it all ``NULL``, no instance will be set to parent instance. For +example:: + + child = await Child.load(parent=Parent).query.gino.first() + +If ``child.parent_id`` is ``NULL`` in database, then the ``child`` instance +won't be called with any ``setattr(child, 'parent', ...)`` at all. (If you need +``child.parent == None`` in this case, consider setting default value +``parent = None`` in child model.) + +Please note, it is deprecated to disable ``none_as_none``, and disabling will +be removed in GINO 1.0. + +0.8.7 (2020-04-19) +^^^^^^^^^^^^^^^^^^ + +* Improved error handling when attribute names collide (Contributed by Reskov in #637 #638) +* Fixed ``with_bind`` usability in aiohttp extension (#518) + +0.8.6 (2020-02-10) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``JSONPathType`` bind processor for asyncpg (#609) +* Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600) +* Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629) +* Added ``distinct()`` to ``Alias`` (#628) + +0.8.5 (2019-11-19) +^^^^^^^^^^^^^^^^^^ + +* Improved support for ``__tablename__`` in ``declared_attr`` (Contributed by Roald Storm in #592) + +0.8.4 (2019-11-09) +^^^^^^^^^^^^^^^^^^ + +* Better loader support for models in subqueries (#573 #585) +* Allowed ``__tablename__`` to be a ``declared_attr`` (#579 #582) +* Fixed Sanic 19.9.0 compatibility (#569) +* Added one() and one_or_none() (Contributed by Ilaï Deutel in #577) +* Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538) +* Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533) +* Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520) +* Fixed Grammar (Contributed by Simeon J Morgan in #504) + +0.8.3 (2019-06-06) +^^^^^^^^^^^^^^^^^^ + +* Fixed deprecated warnings in asyncpg dialect and aiohttp (#425) +* Customizable db attribute name in aiohttp app instance (#457) +* Added Starlette support (#486) + +0.8.2 (2019-03-07) +^^^^^^^^^^^^^^^^^^ + +* Added exception for unknown JSON properties (#406 #408) +* Supported Quart 0.7 (#411) +* Accepted kwargs for db init in extensions (#407 #427) +* Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440) +* Added NullPool (#437 #441) +* Unpinned dependency versions (#447) +* Added support for SQLAlchemy 1.3 (#433 #451) + +0.8.1 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Alias supported ``Label`` (#365) +* Docs update (#308, 4c59ad, #401 by Pascal van Kooten) +* Version requirement for SQLAlchemy is updated to ``>=1.2`` (#378 #382) +* Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) + * And all other extensions (#395) +* Supported Tornado 5 (#396, also thanks to Vladimir Goncharov) +* Fixed custom JSON/JSONB type support (#402 #403) + +(Most fixes done by Tony Wang) + +0.8.0 (2018-10-16) +^^^^^^^^^^^^^^^^^^ + +* Welcome Tony Wang to the maintenance team (#335) +* Allowed custom column names (#261 #297) +* Allowed column instance in ``model.load()`` (Contributed by Jekel in #323) +* [Breaking] Upgraded to aiocontextvars 0.2.0 (#333) +* Fixed bug that the same empty stack is shared between sub-tasks (#313 #334) +* [Breaking] Made ``none_as_none()`` the default behavior (#351) +* Bug fixes and docs update + + +GINO 0.7 +-------- + +This is also version 1.0 beta 3. + +0.7.7 (2018-12-08) +^^^^^^^^^^^^^^^^^^ + +* Backported fix for custom JSON/JSONB type support (#402 #403) + +0.7.6 (2018-08-26) +^^^^^^^^^^^^^^^^^^ + +* Updated library support (Contributed by Tony Wang in #275 #309) +* Added ``none_as_none()`` (#281 #282) +* Added ``ARRAY`` alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289) +* Added ``Model.lookup()`` to prevent updating whole table without primary key (#287 #288) +* Added ``DB_ECHO`` in extension options (Contributed by Mykyta Holubakha in #298) +* Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305) +* Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304) +* Fixed to raise ``UninitializedError`` if bind is ``None`` (Contributed by Tony Wang in #307 #310) + +0.7.5 (2018-07-26) +^^^^^^^^^^^^^^^^^^ + +* Added friendly error message when using abstract models by mistake (#224) +* Supported Python 3.7 (Contributed by Tony Wang in #265) +* Updated documentation +* Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280) + +0.7.4 (2018-06-10) +^^^^^^^^^^^^^^^^^^ + +* Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228) +* Added Quart support (#213) +* Fixed Tornado options parsing (#231) +* Improved coding style and test coverage + +0.7.3 (2018-05-19) +^^^^^^^^^^^^^^^^^^ + +* Fix for failing binary type (#225) + +0.7.2 (2018-05-15) +^^^^^^^^^^^^^^^^^^ + +* Added prepared statement support (#14) +* Added dsn in extension config (Contributed by Yurii Shtrikker in #215) + +0.7.1 (2018-05-03) +^^^^^^^^^^^^^^^^^^ + +* Added support for inline model constraints (Contributed by Kinware in #198) +* Added docs and tests for using SSL (#202) +* Added ``declared_attr`` (#204) +* Allowed ``ModelLoader`` passively load partial model (#216) + +0.7.0 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Added Python 3.5 support (#187) +* Added support to use ``dict`` as ident for ``Model.get`` (#192) +* Added result loader (partial relationship support) (#13) +* Added documentation on relationship and transaction (#146) + + +GINO 0.6 +-------- + +This is also version 1.0 beta 2. + +Migrating to GINO 0.6 +^^^^^^^^^^^^^^^^^^^^^ + +1. Task Local +""""""""""""" + +We created a new Python package aiocontextvars_ from previous ``local.py``. If +you made use of the task local features, you should install this package. + +Previous ``gino.enable_task_local()`` and ``gino.disable_task_local()`` are +replaced by ``aiocontextvars.enable_inherit()`` and +``aiocontextvars.disable_inherit()``. However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars_ by default offers task +local even without ``enable_inherit()``, which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars_ is installed. + +There is no ``gino.get_local()`` and ``gino.reset_local()`` relevant in +aiocontextvars_. The similar thing is ``aiocontextvars.ContextVar`` instance +through its ``get()``, ``set()`` and ``delete()`` methods. + +Previous ``gino.is_local_root()`` is now +``not aiocontextvars.Context.current().inherited``. + +2. Engine +""""""""" + +GINO 0.6 hides ``asyncpg.Pool`` behind the new SQLAlchemy-alike +``gino.GinoEngine``. Instead of doing this in 0.5:: + + async with db.create_pool('postgresql://...') as pool: + # your code here + +You should change it to this in 0.6:: + + async with db.with_bind('postgresql://...') as engine: + # your code here + +This equals to:: + + engine = await gino.create_engine('postgresql://...') + db.bind = engine + try: + # your code here + finally: + db.bind = None + await engine.close() + +Or:: + + engine = await db.set_bind('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Or even this:: + + db = await gino.Gino('postgresql://...') + try: + # your code here + finally: + await db.pop_bind().close() + +Choose whichever suits you the best. + +Obviously ``GinoEngine`` doesn't provide ``asyncpg.Pool`` methods directly any +longer, but you can get the underlying ``asyncpg.Pool`` object through +``engine.raw_pool`` property. + +``GinoPool.get_current_connection()`` is now changed to ``current_connection`` +property on ``GinoEngine`` instances to support multiple engines. + +``GinoPool.execution_option`` is gone, instead ``update_execution_options()`` +on ``GinoEngine`` instance is available. + +``GinoPool().metadata`` is gone, ``dialect`` is still available. + +``GinoPool.release()`` is removed in ``GinoEngine`` and ``Gino``, the +``release()`` method on ``GinoConnection`` object should be used instead. + +These methods exist both in 0.5 ``GinoPool`` and 0.6 ``GinoEngine``: +``close()``, ``acquire()``, ``all()``, ``first()``, ``scalar()``, ``status()``. + +3. GinoConnection +""""""""""""""""" + +Similarly, ``GinoConnection`` in 0.6 is no longer a subclass of +``asyncpg.Connection``, instead it has a ``asyncpg.Connection`` instance, +accessable through ``GinoConnection.raw_connection`` property. + +``GinoConnection.metadata`` is deleted in 0.6, while ``dialect`` remained. + +``GinoConnection.execution_options()`` is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior. + +``GinoConnection.release()`` is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +``permanent=False`` to remain its previous behavior. + +And ``all()``, ``first()``, ``scalar()``, ``status()``, ``iterate()``, +``transaction()`` remained in 0.6. + +4. Query API +"""""""""""" + +All five query APIs ``all()``, ``first()``, ``scalar()``, ``status()``, +``iterate()`` now accept the same parameters as SQLAlchemy ``execute()``, +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter ``bind`` if they did. Just use the API on the ``GinoEngine`` or +``GinoConnection`` object instead. + +5. Transaction +"""""""""""""" + +Transaction interface is rewritten. Now in 0.6, a ``GinoTransaction`` object is +provided consistently from all 3 methods:: + + async with db.transaction() as tx: + # within transaction + + async with engine.transaction() as tx: + # within transaction + + async with engine.acquire() as conn: + async with conn.transaction() as tx: + # within transaction + +And different usage with ``await``:: + + tx = await db.transaction() + try: + # within transaction + await tx.commit() + except: + await tx.rollback() + raise + +The ``GinoConnection`` object is available at ``tx.connection``, while +underlying transaction object from database driver is available at +``tx.transaction`` - for asyncpg it is an ``asyncpg.transaction.Transaction`` +object. + +0.6.6 (2018-05-18) +^^^^^^^^^^^^^^^^^^ + +* Backported a fix for failing binary type (#225) + +0.6.5 (2018-04-18) +^^^^^^^^^^^^^^^^^^ + +* Abandoned 0.6.4 and keep 0.6.x stable +* Backported doc for transaction + +0.6.4 (2018-04-16) +^^^^^^^^^^^^^^^^^^ + +Abandoned version, please use 0.7.0 instead. + +0.6.3 (2018-04-08) +^^^^^^^^^^^^^^^^^^ + +* Added aiohttp support +* Added support for calling ``create()`` on model instances (Contributed by Kinware in #178 #180) +* Fixed ``get()`` by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184) + +0.6.2 (2018-03-24) +^^^^^^^^^^^^^^^^^^ + +* Fixed SQLAlchemy prefetch issue (#141) +* Fixed issue that mixin class on Model not working (#174) +* Added more documentation (Thanks Olaf Conradi for reviewing) + +0.6.1 (2018-03-18) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``create`` and ``drop`` for ``Enum`` type (#160) +* A bit more documentation (#159) + +0.6.0 (2018-03-14) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] API Refactored, ``Pool`` replaced with ``Engine`` + + * New API ``Engine`` replaced asyncpg ``Pool`` (#59) + * Supported different dialects, theoretically + * Used aiocontextvars_ instead of builtin task local (#89) +* [Breaking] Fixed query API with ``multiparams`` (executemany) to return correctly (#20) +* [Breaking] The query methods no longer accept the parameter ``bind`` +* [Breaking] ``Gino`` no longer exposes ``postgresql`` types +* Added ``echo`` on engine (#142) +* Added tests to cover 80% of code +* Added ``gino`` extension on ``SchemaItem`` for ``create_all`` and so on (#76 #106) +* Added ``gino`` extension on model classes for ``create()`` or ``drop()`` +* Added ``_update_request_cls`` on ``CRUDModel`` (#147) +* Rewrote the documentation (#146) + +.. _aiocontextvars: https://github.com/fantix/aiocontextvars + + +GINO 0.5 +-------- + +This is also version 1.0 beta 1. + +0.5.8 (2018-02-14) +^^^^^^^^^^^^^^^^^^ + +* Preparing for 0.6.0 which will be a breaking release +* Fixed wrong value of ``Enum`` in creation (Contributed by Sergey Kovalev in #126) + +0.5.7 (2017-11-24) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.6. + +* Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114) +* Added ``Model.outerjoin`` + +0.5.6 (2017-11-23) +^^^^^^^^^^^^^^^^^^ + +* Changed to use unnamed statement when possible (#80 #90) +* Added more example (Contributed by Kentoseth in #109) +* Added ``Model.join`` and made ``Model`` selectable (Contributed by Ádám Barancsuk in #112 #113) + +0.5.5 (2017-10-18) +^^^^^^^^^^^^^^^^^^ + +* Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87) +* Added ability to reset local storage (#84) +* Fixed bug in JSON property update +* Added update chaining feature + +0.5.4 (2017-10-04) +^^^^^^^^^^^^^^^^^^ + +* Updated example (Contributed by Kinware in #75) +* Added ``Model.insert`` (Contributed by Neal Wang in #63) +* Fixed issue that non-lazy acquiring fails dirty (#79) + +0.5.3 (2017-09-23) +^^^^^^^^^^^^^^^^^^ + +* Fixed ``no module named cutils`` error (Contributed by Vladimir Goncharov in #73) + +0.5.2 (2017-09-10) +^^^^^^^^^^^^^^^^^^ + +* Added missing driver name on dialect (#67) +* Fixed dialect to support native decimal type (#67) + +0.5.1 (2017-09-09) +^^^^^^^^^^^^^^^^^^ + +This is an emergency fix for 0.5.0. + +* Reverted the extension, back to pure Python (#60) +* Used SQLAlchemy ``RowProxy`` +* Added ``first_or_404`` +* Fixed bug that ``GinoPool`` cannot be inherited + +0.5.0 (2017-09-03) +^^^^^^^^^^^^^^^^^^ + +* [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten + + * Extracted CRUD operations + * Core operations are moved to ``dialect`` and execution context + * Removed ``guess_model``, switched to explicit execution options + * Turned ``timeout`` parameter to an execution option + * Extracted ``pool``, ``connection`` and ``api`` from ``asyncpg_delegate`` +* Added support for SQLAlchemy execution options, and a few custom options +* [Breaking] Made `Model.select` return rows by default (#39) +* Moved `get_or_404` to extensions (#38) +* Added iterator on model classes (#43) +* Added Tornado extension (Contributed by Vladimir Goncharov) +* Added `Model.to_dict` (#47) +* Added an extension module to update `asyncpg.Record` with processed results + + +Early Development Releases +-------------------------- + +Considered as alpha releases. + + +0.4.1 (2017-08-20) +^^^^^^^^^^^^^^^^^^ + +* Support ``select`` on model instance + +0.4.0 (2017-08-15) +^^^^^^^^^^^^^^^^^^ + +* Made ``get_or_404`` more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31) +* Delegated ``sqlalchemy.__all__`` (Contributed by Neal Wang in #10 #33) +* [Breaking] Rewrote JSON/JSONB support (#29) +* Added ``lazy`` parameter on ``db.acquire`` (Contributed by Binghan Li in #32) +* Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34) +* Fixed ``iterate`` API to be compatible with asyncpg (#32) +* Unified exceptions +* [Breaking] Changed ``update`` API (#29) +* Bug fixes + +0.3.0 (2017-08-07) +^^^^^^^^^^^^^^^^^^ + +* Supported ``__table_args__`` (#12) +* Introduced task local to manage connection in context (#19) +* Added ``query.gino`` extension for in-place execution +* Refreshed README (#3) +* Adopted PEP 487 (Contributed by Tony Wang in #17 #27) +* Used ``weakref`` on ``__model__`` of table and query (Contributed by Tony Wang) +* Delegated asyncpg ``timeout`` parameter (Contributed by Neal Wang in #16 #22) + +0.2.3 (2017-08-04) +^^^^^^^^^^^^^^^^^^ + +* Supported any primary key (Contributed by Tony Wang in #11) + +0.2.2 (2017-08-02) +^^^^^^^^^^^^^^^^^^ + +* Supported SQLAlchemy result processor +* Added rich support on JSON/JSONB +* Bug fixes + +0.2.1 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Added ``update`` and ``delete`` API + +0.2.0 (2017-07-28) +^^^^^^^^^^^^^^^^^^ + +* Changed API, no longer reuses asyncpg API + +0.1.1 (2017-07-25) +^^^^^^^^^^^^^^^^^^ + +* Added ``db.bind`` +* API changed: parameter ``conn`` renamed to optional ``bind`` +* Delegated asyncpg Pool with ``db.create_pool`` +* Internal enhancement and bug fixes + +0.1.0 (2017-07-21) +^^^^^^^^^^^^^^^^^^ + +* First release on PyPI. diff --git a/docs/zh/master/_sources/tutorials.rst.txt b/docs/zh/master/_sources/tutorials.rst.txt new file mode 100644 index 0000000..6b3bb98 --- /dev/null +++ b/docs/zh/master/_sources/tutorials.rst.txt @@ -0,0 +1,9 @@ +Tutorials +========= + +.. toctree:: + :glob: + + tutorials/announcement.rst + tutorials/tutorial.rst + tutorials/* diff --git a/docs/zh/master/_sources/tutorials/announcement.rst.txt b/docs/zh/master/_sources/tutorials/announcement.rst.txt new file mode 100644 index 0000000..f159e02 --- /dev/null +++ b/docs/zh/master/_sources/tutorials/announcement.rst.txt @@ -0,0 +1,297 @@ +官宣:Python 异步编程再添一利器 +=============================== + + GINO 填补了国内外 asyncio ORM 领域的空白 + +随着 Tornado\ [1]_ 和 asyncio\ [2]_ 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。\ +在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +`GINO `__ 了解一下! + +.. image:: ../images/python-gino.webp + :align: center + + +GINO 是谁 +--------- + +GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义\ [3]_\ 手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,\ +你肯定要问一句“这是谁”。 + +ORM,即关系对象映射(Object-Relational Mapping\ [4]_\ ),是一类开发人员喜闻乐见的效率工具,\ +它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢? + +因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性\ +(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),\ +创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:\ +性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 ``current_user.name`` +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试? + +传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,\ +但应用到大型项目中却十分考验开发人员的平均水平。 + +所有这些问题如果再放进异步编程的环境里,那就是 O(n\ :sup:`2`) 的复杂度了——哦不,是 +O(2\ :sup:`n`)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。 + +所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,\ +我索性在 1.0 稳定版发布的前夕做个年终总结。 + + +先说“方便快捷” +-------------- + + “方便快捷”主要说的是开发效率。 + +重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,\ +开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。 + +:: + + from gino import Gino + db = Gino() + class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String) + +这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟? + +没有错,这就是 SQLAlchemy ORM\ [5]_ 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core\ [6]_\ (SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道\ +(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic\ [7]_\ 、各种 SQLAlchemy 的增强插件\ [8]_\ 、专业领域的 PostGIS/geoalchemy\ [9]_\ +等,GINO 全都兼容。 + +是不是十分方便、十分快捷?不止这样。 + +GINO 一站式地解决了常用 CRUD 快捷方式\ [10]_\ 、上下文管理(aiocontextvars\ [11]_\ [12]_\ )、 +数据库事务封装和嵌套\ [13]_\ 、连接池管理和懒加载\ [14]_\ 等多项便捷功能,无额外依赖关系,即装即用。 + +:: + + daisy = await User.create(name="daisy") + await daisy.update(name="Daisy").apply() + +GINO 还提供了\ [15]_\ 各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado\ [1]_\ 、\ +aiohttp\ [16]_\ 、Sanic\ [17]_\ 、FastAPI\ [18]_\ /Starlette\ [19]_\ 、Quart\ [20]_\ +什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。 + +为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa\ [21]_ 的降维打击: + +1. 最少侵入型\ [22]_\ :SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。 +2. 终身不婚型\ [23]_\ :天生厌恶“对象”,只愿定义“表”,空手接 SQL。 +3. 火力全开型\ [24]_\ :最大程度的便利,非典型异步 ORM。 + +最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、\ +一秒可读百万行的 asyncpg\ [25]_\ ,以及 uvloop\ [26]_\ (可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,\ +深受俄罗斯和乌克兰人民的爱戴。 + + +再说“简单明了” +-------------- + + Explicit is better than implicit. + + Simple is better than complex. + + –The Zen of Python, PEP 20\ [27]_ + +Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,\ +因此 GINO 的很多设计都受到了明确性的影响。 + +比如说,GINO 的 ``Model`` 是完全无状态的普通 Python 对象(POPO\ [28]_\ —— 例如前面的 ``User`` +类,它的实例 ``daisy`` 就是内存里面的常规对象,你可以用 ``daisy.name`` 访问属性,也可以用 +``daisy.name = "DAISY"`` 来修改属性,或者用 ``u = User()`` 来创建新的实例,\ +这些操作都不会访问数据库,绝对绿色环保无毒副作用。 + +等到需要操作数据库的时候,你一定会有感知的。比如执行 ``INSERT`` 要用 +``u = await User.create()``\ ,而 ``UPDATE`` 则是 +``await u.update(name="Daisy").apply()``\ 。 + +.. hint:: + + 其中,``u.update(name="Daisy")`` 与 ``u.name = "Daisy"`` 类似,\ + 都是只在内存里修改对象的属性,不同的是 ``u.update()`` 还会返回一个包含本次变更的中间结果,\ + 对其执行 ``await xxx.apply()`` 则会将这些变更应用到数据库里。 + +这里的 ``await`` 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 ``await`` +就没有数据库操作。 + +另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders\ [29]_\ 。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 ``u = await User.get(1)`` 可以直接获取到 ID 为 ``1`` 的用户对象。\ +但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。\ +加载器的用法也是很简单的,比如一个用户可能写了很多本书: + +:: + + class Book(db.Model): + __tablename__ = "books" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + author_id = db.ForeignKey("users.id") + +然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者: + +:: + + query = Book.outerjoin(User).select() + loader = Book.load(author=User) + async for book in query.gino.load(loader).iterate(): + print(book.title, "written by", book.author.name) + +很简单的一个外连接查询 ``Book.outerjoin(User)``\ ,配合一个直观的加载器 +``Book.load(author=User)``\ ,就实现了: + +1. 执行 ``SELECT * FROM books LEFT JOIN users ON ...``\ ; +2. 将数据库返回结果的每一行中,属于 ``books`` 的字段加载成一个 ``Book`` 实例; +3. 然后将该行中剩下的属于 ``users`` 的字段加载成一个 ``User`` 实例; +4. 最后将 ``User`` 实例设置到 ``Book`` 实例的 ``author`` 属性上。 + +既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,\ +精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。 + + +优势与不足 +---------- + +随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM\ [30]_\ 、ORM\ [31]_\ (是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。 + +GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候\ +不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、\ +快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,\ +可以谨慎地用于生产环境了。 + +以下是近来统计到的关于 GINO 的应用案例: + +- 笔者工作上开发的一个服务:\ https://github.com/uc-cdis/metadata-service +- 还是笔者自己写的一个工具:\ https://github.com/fantix/aintq +- 一个汇率 API 服务:\ https://exchangeratesapi.io/ + + .. image:: ../images/exchangeratesapi.webp + :align: center + +- 各种 Telegram、Discord 的 Bot。 +- ArchLinux 用户包:\ https://aur.archlinux.org/packages/python-gino/ + + .. image:: ../images/archlinux.webp + :align: center + +- https://github.com/bryanforbes/gino-stubs/ +- 俄语教程:\ https://www.youtube.com/watch?v=WW4rOnfhiQY +- 高性能模板项目:\ https://github.com/leosussan/fastapi-gino-arq-uvicorn +- 还有几个商用的,但没征得同意就不贴出来了。 + +另外,GINO 还贴心地提供了中文文档\ [32]_\ ,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!): + +.. image:: ../images/docs.webp + :align: center + +GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能\ +(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。 + + +建设社会主义 +------------ + +GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 **4888 元**\ 的 PyCharm +专业版全家桶 License 一枚\ [33]_\ (没有发票)。 + +目前急需帮助的有: + +1. 各个 Web 框架插件的维护工作需要多人认领; +2. 更多的例子和文档,以及中文、俄文的翻译; +3. MySQL 的支持。 + +以及下面这些一直需要的帮助: + +1. 用 GINO,找 bug,提建议; +2. 修 bug,做功能,提 PR; +3. 维护社区,回答问题,参与讨论; +4. 最后也是最重要的:\ `去 GitHub 上给 GINO 加一颗星星! + `__ + +.. image:: ../images/happy-hacking.png + :align: center + + +关于作者 +-------- + +87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。\ +小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,\ +期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、\ +新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。\ +业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P + +特别感谢 `Tony Wang `__ 对 GINO 项目的贡献,以及\ `所有人 +`__\ 的贡献。 + + +参考文献 +-------- + +.. [1] Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado. +.. [2] Python Software Foundation. asyncio — 异步 I/O. + https://docs.python.org/zh-cn/3/library/asyncio.html. +.. [3] 维基百科. GNU. https://zh.wikipedia.org/wiki/GNU. +.. [4] 维基百科. 对象关系映射. + `https://zh.wikipedia.org/wiki/对象关系映射 + `__. +.. [5] 维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy. +.. [6] Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. + https://docs.sqlalchemy.org/en/13/core/index.html. +.. [7] Michael Bayer. Welcome to Alembic’s documentation!. + https://alembic.sqlalchemy.org/en/latest/. +.. [8] Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. + https://github.com/dahlia/awesome-sqlalchemy. +.. [9] Ricardo GarciaSilva. Does it have postgis support?. + https://github.com/python-gino/gino/issues/627. +.. [10] Fantix King. GINO 基础教程 - 增删改查. + https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations. +.. [11] Python Software Foundation. contextvars —Context Variables. + https://docs.python.org/3/library/contextvars.html. +.. [12] Fantix King. gino/aiocontextvars.py. + https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py. +.. [13] Fantix King. 数据库事务. + https://python-gino.org/docs/zh/master/how-to/transaction.html. +.. [14] Fantix King. 引擎与连接. + https://python-gino.org/docs/zh/master/explanation/engine.html. +.. [15] GINO Community. GINO Community. https://github.com/python-gino/. +.. [16] aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/. +.. [17] Sanic Community. SanicFramework. https://sanicframework.org/. +.. [18] Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/. +.. [19] Encode OSS. Starlette. https://www.starlette.io/. +.. [20] Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/. +.. [21] Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. + https://github.com/CanopyTax/asyncpgsa. +.. [22] Fantix King. 表结构定义 -GINO 引擎. + https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine. +.. [23] Fantix King. 表结构定义 - GINO core.  + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core. +.. [24] Fantix King. 表结构定义 - GINO ORM. + https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm. +.. [25] MagicStack Inc. + asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. + https://github.com/MagicStack/asyncpg. +.. [26] MagicStack Inc. uvloop -Ultra fast asyncio event loop. + https://github.com/MagicStack/uvloop. +.. [27] Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/. +.. [28] Wikipedia. Plain old Java object. + https://en.wikipedia.org/wiki/Plain_old_Java_object. +.. [29] Fantix King. 加载器与关系. + https://python-gino.org/docs/zh/master/how-to/loaders.html. +.. [30] Andrey Bondar. Tortoise ORM. https://tortoise.github.io/. +.. [31] Encode OSS. ORM - An async ORM. https://github.com/encode/orm. +.. [32] Fantix King. 欢迎来到 GINO 的文档!. + https://python-gino.org/docs/zh/master/index.html. +.. [33] JetBrains s.r.o. Open Source Licenses. + https://www.jetbrains.com/community/opensource/#support. diff --git a/docs/zh/master/_sources/tutorials/fastapi.rst.txt b/docs/zh/master/_sources/tutorials/fastapi.rst.txt new file mode 100644 index 0000000..aa2d582 --- /dev/null +++ b/docs/zh/master/_sources/tutorials/fastapi.rst.txt @@ -0,0 +1,669 @@ +Build a FastAPI Server +====================== + +In this tutorial, we'll build a production-ready FastAPI_ server together. +The full functional example is available `here +`__. + +Our application stack will look like this: + +.. image:: ../images/gino-fastapi.svg + :align: center + + +.. _FastAPI: https://fastapi.tiangolo.com/ + + +Start a New Project +------------------- + +Instead of pip, let's use the shiny Poetry_ to manage our project. Follow the link to +`install Poetry `_, and create our new +project in an empty directory: + + +.. code-block:: console + + $ mkdir gino-fastapi-demo + $ cd gino-fastapi-demo + $ git init + $ poetry init + +Then follow the Poetry_ guide to finish the initialization - you may say "no" to the +interactive dependency creation, because we will add them manually. It is okay to use +the default values in the other steps of the guide, and make sure the package name +remains ``gino-fastapi-demo``. + +.. _Poetry: https://python-poetry.org/ + + +Add Dependencies +---------------- + +FastAPI_ is built on top of the Starlette_ framework, so we shall use the `GINO +extension for Starlette `_. Simply run: + +.. code-block:: console + + $ poetry add gino[pg,starlette] + +Then let's add FastAPI_, together with the lightning-fast ASGI_ server Uvicorn_, and +Gunicorn_ as a production application server: + +.. code-block:: console + + $ poetry add fastapi uvicorn gunicorn + +For database migration, we'll use Alembic_. Because it uses normal DB-API_, we need +psycopg_ here too: + +.. code-block:: console + + $ poetry add alembic psycopg2 + +At last, let's add pytest_ in the development environment for testing. We also want to +add the requests_ library to use the Starlette_ |TestClient|_: + +.. code-block:: console + + $ poetry add -D pytest requests + +.. hint:: + + With the steps above, Poetry_ will automatically create a virtualenv_ for you + behind the scene, and all the dependencies are installed there. We will assume + using this for the rest of the tutorial. But you're free to create your own + virtualenv_, and Poetry_ will honor it when it's activated. + +That's all, this is my ``pyproject.toml`` created by Poetry_, yours should look similar: + +.. code-block:: toml + + [tool.poetry] + name = "gino-fastapi-demo" + version = "0.1.0" + description = "" + authors = ["Fantix King "] + + [tool.poetry.dependencies] + python = "^3.8" + gino = {version = "^1.0", extras = ["pg", "starlette"]} + fastapi = "^0.54.1" + uvicorn = "^0.11.3" + gunicorn = "^20.0.4" + alembic = "^1.4.2" + psycopg2 = "^2.8.5" + + [tool.poetry.dev-dependencies] + pytest = "^5.4.1" + requests = "^2.23.0" + + [build-system] + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" + +.. image:: ../images/gino-fastapi-poetry.svg + :align: right + +And there's also an auto-generated ``poetry.lock`` file with the frozen versions. The +directory layout should look like the diagram on the right. Now let's add the two files +to the Git repository (we will skip showing these git operations in future steps): + +.. code-block:: console + + $ git add pyproject.toml poetry.lock + $ git commit -m 'add project dependencies' + +.. _Starlette: https://www.starlette.io/ +.. _ASGI: https://asgi.readthedocs.io/ +.. _Uvicorn: https://www.uvicorn.org/ +.. _Gunicorn: https://gunicorn.org/ +.. _Alembic: https://alembic.sqlalchemy.org/ +.. _DB-API: https://www.python.org/dev/peps/pep-0249/ +.. _psycopg: https://www.psycopg.org/ +.. _pytest: https://docs.pytest.org/ +.. _virtualenv: https://virtualenv.pypa.io/ +.. _requests: https://requests.readthedocs.io/ + + +Write a Simple Server +--------------------- + +Now let's write some Python code. + +We'll create an extra ``src`` directory to include all the Python files, as demonstrated +in the diagram below. This is known as the "`src layout +`_" providing a cleaner hierarchy. + +.. image:: ../images/gino-fastapi-src.svg + :align: right + +The root Python package of our project is named as ``gino_fastapi_demo``, under which we +will create two Python modules: + +* ``asgi`` as the ASGI entry point - we'll feed it to the ASGI server +* ``main`` to initialize our server + +Here's ``main.py``:: + + from fastapi import FastAPI + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + return app + +And we'll simply instantiate our application in ``asgi.py``:: + + from .main import get_app + + app = get_app() + +Then run ``poetry install`` to link our Python package into the ``PYTHONPATH`` in +development mode. We'll be able to start a Uvicorn development server after that: + +.. code-block:: console + + $ poetry install + Installing dependencies from lock file + + No dependencies to install or update + + - Installing gino-fastapi-demo (0.1.0) + + $ poetry run uvicorn gino_fastapi_demo.asgi:app --reload + INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + INFO: Started reloader process [53010] + INFO: Started server process [53015] + INFO: Waiting for application startup. + INFO: Application startup complete. + +The ``--reload`` option enables Uvicorn to automatically reload the server for us when +the Python source code is updated. Now access http://127.0.0.1:8000/docs to see the +Swagger UI of our new FastAPI server. + +.. hint:: + + As mentioned previously, if you're in your own virtualenv, the command ``poetry run + uvicorn`` can be simplified as just ``uvicorn``. + + ``poetry run`` is a convenient shortcut to run the following command in the + virtualenv managed by Poetry. + + +Add GINO Extension +------------------ + +.. image:: ../images/gino-fastapi-config.svg + :align: right + +Now let's add GINO to our server. + +First of all, we need a way to configure the database. In this tutorial, we'll use the +`configuration system `_ from Starlette_. +Add ``src/gino_fastapi_demo/config.py`` as follows:: + + from sqlalchemy.engine.url import URL, make_url + from starlette.config import Config + from starlette.datastructures import Secret + + config = Config(".env") + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + DB_DSN = config( + "DB_DSN", + cast=make_url, + default=URL( + drivername=DB_DRIVER, + username=DB_USER, + password=DB_PASSWORD, + host=DB_HOST, + port=DB_PORT, + database=DB_DATABASE, + ), + ) + DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1) + DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16) + DB_ECHO = config("DB_ECHO", cast=bool, default=False) + DB_SSL = config("DB_SSL", default=None) + DB_USE_CONNECTION_FOR_REQUEST = config( + "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True + ) + DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1) + DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1) + +This config file will load from environment variable first, if not found then from a +file named ``.env`` from current path (usually the project root directory), and at last +use the default value defined above. For example, you can either overwrite in CLI +directly like this: + +.. code-block:: console + + $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload + +.. image:: ../images/gino-fastapi-env.svg + :align: right + +Or set them in the file ``.env`` (this file must not be committed into Git, remember to +add it to ``.gitignore``): + +.. code-block:: bash + + DB_HOST=localhost + DB_USER=postgres + +Now it's time to create a PostgreSQL_ database and set the connection variables +correctly here. This is usually something like ``createdb yourdbname``, but it may vary +across different platforms, so we won't cover this part in this tutorial. + +.. tip:: + + Alternatively, you could also set ``DB_DSN`` to for example + ``postgresql://user:password@localhost:5432/dbname`` to override the other individual + config values like ``DB_HOST`` defined before ``DB_DSN``. + + If defined, ``DB_DSN`` always have the higher priority over the individual ones, + regardless of where they are defined - even if ``DB_HOST`` is defined in environment + variable and ``DB_DSN`` is defined in ``.env`` file, ``DB_HOST`` is still ignored. + Default value doesn't count. + +.. image:: ../images/gino-fastapi-models.svg + :align: right + +Then, create a new Python sub-package ``gino_fastapi_demo.models`` to encapsulate +database-related code, and add the code below to +``src/gino_fastapi_demo/models/__init__.py``:: + + from gino.ext.starlette import Gino + + from .. import config + + db = Gino( + dsn=config.DB_DSN, + pool_min_size=config.DB_POOL_MIN_SIZE, + pool_max_size=config.DB_POOL_MAX_SIZE, + echo=config.DB_ECHO, + ssl=config.DB_SSL, + use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST, + retry_limit=config.DB_RETRY_LIMIT, + retry_interval=config.DB_RETRY_INTERVAL, + ) + +At last, modify ``src/gino_fastapi_demo/main.py`` to install the GINO extension: + +.. code-block:: diff + + from fastapi import FastAPI + + + +from .models import db + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + + db.init_app(app) + return app + +Saving the file, you should see the Uvicorn server reloads our changes and connects to +the database: + +.. code-block:: console + + WARNING: Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading... + INFO: Shutting down + INFO: Waiting for application shutdown. + INFO: Application shutdown complete. + INFO: Finished server process [63562] + INFO: Started server process [63563] + INFO: Waiting for application startup. + INFO: Connecting to the database: postgresql://fantix:***@localhost + INFO: Database connection pool created: + INFO: Application startup complete. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Create Models and API +--------------------- + +.. image:: ../images/gino-fastapi-models-users.svg + :align: right + +It's time to implement the API now. Let's say we are building a user management service, +through which we could add users, list users and delete users. + +First of all, we need a database table ``users`` to store the data, mapped to a GINO +model named ``User``. We shall add the model in ``gino_fastapi_demo.models.users``:: + + from . import db + + class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.BigInteger(), primary_key=True) + nickname = db.Column(db.Unicode(), default="unnamed") + +.. image:: ../images/gino-fastapi-views.svg + :align: right + +The model definition is simple enough to explain itself. + +Then we only have to use it properly in the API implementation, for which we'll create a +new Python sub-package ``gino_fastapi_demo.views``, and a new module +``gino_fastapi_demo.views.users`` as follows:: + + from fastapi import APIRouter + from pydantic import BaseModel + + from ..models.users import User + + router = APIRouter() + + @router.get("/users/{uid}") + async def get_user(uid: int): + user = await User.get_or_404(uid) + return user.to_dict() + + class UserModel(BaseModel): + name: str + + @router.post("/users") + async def add_user(user: UserModel): + rv = await User.create(nickname=user.name) + return rv.to_dict() + + @router.delete("/users/{uid}") + async def delete_user(uid: int): + user = await User.get_or_404(uid) + await user.delete() + return dict(id=uid) + + def init_app(app): + app.include_router(router) + +The |APIRouter|_ holds our new APIs locally, and ``init_app`` is used to integrate it +into our FastAPI application. Here we want some `inversion of control`_: let's make the +APIs plugable, so that we don't have to import all possible future views manually. We +shall use the `Entry Points`_ feature to load the dependencies. Add this code below to +``gino_fastapi_demo.main``:: + + import logging + from importlib.metadata import entry_points + + logger = logging.getLogger(__name__) + + def load_modules(app=None): + for ep in entry_points()["gino_fastapi_demo.modules"]: + logger.info("Loading module: %s", ep.name) + mod = ep.load() + if app: + init_app = getattr(mod, "init_app", None) + if init_app: + init_app(app) + +.. hint:: + + If you're running Python < 3.8, you'll need this `importlib-metadata backport + `_. + +And call it in our application factory: + +.. code-block:: diff + + def get_app(): + app = FastAPI(title="GINO FastAPI Demo") + db.init_app(app) + + load_modules(app) + return app + +Finally, define the entry points in ``pyproject.toml`` following the `Poetry document +for plugins `_: + +.. code-block:: toml + + [tool.poetry.plugins."gino_fastapi_demo.modules"] + "users" = "gino_fastapi_demo.views.users" + +Run ``poetry install`` again to activate the entry points - you may need to restart the +Uvicorn_ development server manually, as the reloader cannot capture the changes we made +to ``pyproject.toml``. + +Now you should be able to see the 3 new APIs on the Swagger UI. But none of them works, +because we still haven't created the database tables. + +.. |APIRouter| replace:: ``APIRouter`` +.. _APIRouter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter +.. _inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control +.. _Entry Points: https://docs.python.org/3/library/importlib.metadata.html#entry-points + + +Integrate with Alembic +---------------------- + +To get started with Alembic_, run this command in the project root directory: + +.. code-block:: console + + $ poetry run alembic init migrations + +.. image:: ../images/gino-fastapi-alembic.svg + :align: right + +This will generate a new directory ``migrations`` where Alembic_ will store database +migration revisions. At the same time, an ``alembic.ini`` file is created in the project +root directory. Let's simply add all of them to Git control. + +For Alembic_ to use our data models defined with GINO (and of course the database +config), we need to modify ``migrations/env.py`` to connect with the GINO instance: + +.. code-block:: diff + + # add your model's MetaData object here + # for 'autogenerate' support + # from myapp import mymodel + # target_metadata = mymodel.Base.metadata + -target_metadata = None + +from gino_fastapi_demo.config import DB_DSN + +from gino_fastapi_demo.main import db, load_modules + + + +load_modules() + +config.set_main_option("sqlalchemy.url", str(DB_DSN)) + +target_metadata = db + +Then create our first migration revision with: + +.. code-block:: console + + $ poetry run alembic revision --autogenerate -m 'add users table' + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.autogenerate.compare] Detected added table 'users' + Generating migrations/versions/32c0feba61ea_add_users_table.py ... done + +The generated revision file should roughly look like this:: + + def upgrade(): + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("nickname", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + def downgrade(): + op.drop_table("users") + +.. hint:: + + Whenever there is a change to the database schema in the future, just modify the + GINO models and run ``alembic revision --autogenerate`` again to generate new + revisions to track the change. Remember to review the revision file - you may want + to adjust it. + +Eventually, let's apply this migration, by upgrading to the latest revision: + +.. code-block:: console + + $ poetry run alembic upgrade head + INFO [alembic.runtime.migration] Context impl PostgresqlImpl. + INFO [alembic.runtime.migration] Will assume transactional DDL. + INFO [alembic.runtime.migration] Running upgrade -> 32c0feba61ea, add users table + +Now all the APIs should be fully operational, try with the Swagger UI. + + +Write the Tests +--------------- + +In order not to break our development database with running tests, let's create a +separate database to run tests. Apply this change to ``gino_fastapi_demo.config``: + +.. code-block:: diff + + config = Config(".env") + + +TESTING = config("TESTING", cast=bool, default=False) + + DB_DRIVER = config("DB_DRIVER", default="postgresql") + DB_HOST = config("DB_HOST", default=None) + DB_PORT = config("DB_PORT", cast=int, default=None) + DB_USER = config("DB_USER", default=None) + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None) + DB_DATABASE = config("DB_DATABASE", default=None) + +if TESTING: + + if DB_DATABASE: + + DB_DATABASE += "_test" + + else: + + DB_DATABASE = "gino_fastapi_demo_test" + DB_DSN = config( + +.. hint:: + + You need to run ``createdb`` to actually create the database. If you have set + ``DB_DATABASE`` in ``.env`` - e.g. ``DB_DATABASE=mydb``, the name of the testing + database should be ``mydb_test``. Or else, ``gino_fastapi_demo_test``. + +Then, let's create our pytest_ fixture in ``tests/conftest.py``:: + + import pytest + from alembic.config import main + from starlette.config import environ + from starlette.testclient import TestClient + + environ["TESTING"] = "TRUE" + + @pytest.fixture + def client(): + from gino_fastapi_demo.main import db, get_app + + main(["--raiseerr", "upgrade", "head"]) + + with TestClient(get_app()) as client: + yield client + + main(["--raiseerr", "downgrade", "base"]) + +.. image:: ../images/gino-fastapi-tests.svg + :align: right + +This fixture creates all the database tables before running the test, yield a Starlette_ +|TestClient|_, and drop all the tables with all the data after the test to maintain a +clean environment for the next test. + +Here's a sample test in ``tests/test_users.py``:: + + import uuid + + def test_crud(client): + # create + nickname = str(uuid.uuid4()) + r = client.post("/users", json=dict(name=nickname)) + r.raise_for_status() + + # retrieve + url = f"/users/{r.json()['id']}" + assert client.get(url).json()["nickname"] == nickname + + # delete + client.delete(url).raise_for_status() + assert client.get(url).status_code == 404 + +Then run the test: + +.. code-block:: console + + $ poetry run pytest + =========================== test session starts =========================== + platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 + rootdir: gino-fastapi-demo + collected 1 item + + tests/test_users.py . [100%] + + ============================ 1 passed in 1.21s ============================ + +.. |TestClient| replace:: ``TestClient`` +.. _TestClient: https://www.starlette.io/testclient/ + + +Notes for Production +-------------------- + +Given the popularity of Docker/Kubernetes, we'll build a ``Dockerfile`` for our demo: + +.. code-block:: dockerfile + + FROM python:3.8-alpine as base + + FROM base as builder + RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev + RUN pip install poetry + COPY . /src/ + WORKDIR /src + RUN python -m venv /env && . /env/bin/activate && poetry install + + FROM base + RUN apk add --no-cache postgresql-libs + COPY --from=builder /env /env + COPY --from=builder /src /src + WORKDIR /src + CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"] + +In this ``Dockerfile``, we used 2 phases to separate the building from the production +image to reduce target artifact size. Also, we are using Gunicorn_ with +|UvicornWorker|_ from Uvicorn_ as the worker class for best production reliability. + +Let's review what we have in the project. + +.. image:: ../images/gino-fastapi-layout.svg + :align: center + +This is the end of the tutorial to build a demo. Below is an incomplete checklist to +go live: + +* Set ``DB_RETRY_LIMIT`` to a larger number to allow staring the application server + before the database is fully ready. +* Implement the same retry logic in ``migrations/env.py`` so that Alembic_ gets the same + functionality. +* Enable ``DB_SSL`` if needed. +* Write a ``docker-compose.yml`` for other developers to get a quick taste or even use + it for development. +* Enable CI_, install ``pytest-cov`` and use ``--cov-fail-under`` to guarantee coverage. +* Integrate static code analysis tools and security/CVE checking tools. +* Automate Alembic_ upgrade properly - e.g. after new version is deployed. +* Be aware of the common security attacks like CSRF_, XSS_, etc. +* Write load tests. + +Again, the source code of the demo is available `here +`__, +and the source of this tutorial is `here +`__. +Please feel free to submit PRs to fix issues or add your thoughts. Happy hacking! + +.. |UvicornWorker| replace:: ``UvicornWorker`` +.. _UvicornWorker: https://www.uvicorn.org/deployment/#gunicorn +.. _CI: https://en.wikipedia.org/wiki/Continuous_integration +.. _CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/docs/zh/master/_sources/tutorials/tutorial.rst.txt b/docs/zh/master/_sources/tutorials/tutorial.rst.txt new file mode 100644 index 0000000..d9527f3 --- /dev/null +++ b/docs/zh/master/_sources/tutorials/tutorial.rst.txt @@ -0,0 +1,436 @@ +GINO Basics +=========== + +This tutorial helps beginners to get started with the basic part of GINO. +Target audiences of this tutorial should have basic knowledge of: + +* RDBMS, especially PostgreSQL_ +* `Asynchronous programming in Python `_ + +Knowledge of SQLAlchemy_ is not required. + +.. _PostgreSQL: https://www.postgresql.org/ + + +Introduction +------------ + +Simply speaking, GINO helps you write and execute raw SQL in your asynchronous +application. Instead of interacting RDBMS directly with raw SQL, you can access +your data through friendly objective API. + +You may not need GINO, or else to say asynchronous database connection, because +it adds quite some complexity and risk to your stack, and it won't make your +code run faster, if not slower. Please read :doc:`/explanation/why` for more +information. + + +Installation +------------ + +To install GINO, run this command in your terminal: + +.. code-block:: console + + $ pip install gino + +This is the preferred method to install GINO, as it will always install the +most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +Alternatively, if you are using Poetry_ to manage your project dependencies, +you may want to run: + +.. code-block:: console + + $ poetry add gino + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ +.. _Poetry: https://python-poetry.org + + +Declare Models +-------------- + +First of all, we'll need a :class:`~gino.api.Gino` object, usually under the +name of ``db`` as a global variable:: + + from gino import Gino + + db = Gino() + +``db`` acts like a reference to the database, most database interactions will +go through it. + +"Model" is a basic concept in GINO, it is a Python class inherited from +:attr:`db.Model `. Each :class:`~gino.declarative.Model` +subclass maps to one table in the database, while each object of the class +represents one row in the table. This must feel familiar if ORM is not a +strange word to you. Now let's declare a model:: + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + +By declaring this ``User`` class, we are actually defining a database table +named ``users``, with two columns ``id`` and ``nickname``. Note that the fixed +:attr:`~gino.declarative.Model.__tablename__` property is required. GINO +suggests singular for model names, and plural for table names. Each +:class:`db.Column ` property defines one column for +the table, where its first parameter indicates the column type in database, +while the rest is for other column attributes or constraints. You can find a +mapping of database types to ``db`` types `here +`__ in the SQLAlchemy +documentation. + +.. note:: + + SQLAlchemy_ is a powerful ORM library for non-asynchronous programming in + Python, on top of which GINO is built. SQLAlchemy supports many popular + RDBMS including PostgreSQL and MySQL through different dialect + implementation, so that the same Python code can be compiled into different + SQL depending on the dialect you choose. GINO inherited this support too, + but for now there is only one dialect for PostgreSQL through asyncpg_. + +.. _asyncpg: https://github.com/MagicStack/asyncpg +.. _SQLAlchemy: https://www.sqlalchemy.org/ + +If you need constraints or indexes covering multiple columns these are also +defined using properties in model classes. The property names must be unique, +but are otherwise not used. Example:: + + class Booking(db.Model): + __tablename__ = 'bookings' + + day = db.Column(db.Date) + booker = db.Column(db.String) + room = db.Column(db.String) + + _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey') + _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True) + _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room') + +It is also possible to define model constraints and indexes outside the model +class if that is preferred. For more details on constraints and indexes, see +`here `__ in the +SQLAlchemy documentation. + +Due to implementation limitations it is currently not allowed to specify +explicit constraints and indexes as direct attributes in classes that are meant +to be subclassed. The same is true for constraints and indexes specified +through the :attr:`~gino.declarative.Model.__table_args__` attribute. In order +to e.g. define constraints in mixin classes, +:func:`~gino.declarative.declared_attr` is required. Please feel free to read +more about it in its API documentation. + + +Get Connected +------------- + +The declaration only defined the mapping, it does not create the actual table +in the database. To do that, we need to get connected first. Let's create a +PostgreSQL database for this tutorial: + +.. code-block:: console + + $ createdb gino + +Then we tell our ``db`` object to connect to this database:: + + import asyncio + + async def main(): + await db.set_bind('postgresql://localhost/gino') + + asyncio.get_event_loop().run_until_complete(main()) + +If this runs successfully, then you are connected to the newly created database. +Here ``postgresql`` indicates the database dialect to use (the default driver +is ``asyncpg``, you can explicitly specify that with ``postgresql+asyncpg://``, +or simply ``asyncpg://``), ``localhost`` is where the server is, and ``gino`` +is the name of the database. Check `here +`__ for more +information about how to compose this database URL. + +.. note:: + + Under the hood :meth:`~gino.api.Gino.set_bind` calls + :func:`~gino.create_engine` and bind the engine to this ``db`` object. GINO + engine is similar to SQLAlchemy engine, but not identical. Because GINO + engine is asynchronous, while the other is not. Please refer to the API + reference of GINO for more information. + +Now that we are connected, let's create the table in database (in the same +``main()`` method):: + + await db.gino.create_all() + +.. warning:: + + It is :meth:`db.gino.create_all `, + not :meth:`db.create_all `, because + ``db`` is inherited from SQLAlchemy :class:`~sqlalchemy.schema.MetaData`, + and :meth:`db.create_all ` is from + SQLAlchemy using non-asynchronous methods, which doesn't work with the + bound GINO engine. + + In practice :meth:`~gino.schema.GinoSchemaVisitor.create_all` is usually + not an ideal solution. To manage database schema, tool like Alembic_ is + recommended, please see how to :doc:`/how-to/alembic`. + +If you want to explicitly disconnect from the database, you can do this:: + + await db.pop_bind().close() + +Let's review the code we have so far together in one piece before moving on:: + + import asyncio + from gino import Gino + + db = Gino() + + + class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer(), primary_key=True) + nickname = db.Column(db.Unicode(), default='noname') + + + async def main(): + await db.set_bind('postgresql://localhost/gino') + await db.gino.create_all() + + # further code goes here + + await db.pop_bind().close() + + + asyncio.get_event_loop().run_until_complete(main()) + +.. _Alembic: https://bitbucket.org/zzzeek/alembic + + +CRUD Operations +--------------- + +In order to operate on the database, one of GINO's core features is to Create, +Retrieve, Update or Delete model objects, also known as the CRUD operations. + + +Create +^^^^^^ + +Let's start by creating a ``User``:: + + user = await User.create(nickname='fantix') + # This will cause GINO to execute this SQL with parameter 'fantix': + # INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname + +As mentioned previously, ``user`` object represents the newly created row in +the database. You can get the value of each columns by the declared column +properties on the object:: + + print(f'ID: {user.id}') # 1 + print(f'Nickname: {user.nickname}') # fantix + +It is also possible to create a model instance in-memory first, modify it, then +finally create it in the database:: + + user = User(nickname='fantix') + user.nickname += ' (founder)' + await user.create() + +Retrieve +^^^^^^^^ + +To retrieve a model object from database by primary key, you can use the class +method :meth:`~gino.crud.CRUDModel.get` on the model class. Now let's retrieve +the same row:: + + user = await User.get(1) + # SQL (parameter: 1): + # SELECT users.id, users.nickname FROM users WHERE users.id = $1 + +Normal SQL queries are done through a class property +:attr:`~gino.crud.CRUDModel.query`. For example, let's retrieve all ``User`` +objects from database as a list:: + + all_users = await db.all(User.query) + # SQL: + # SELECT users.id, users.nickname FROM users + +Alternatively, you can use the ``gino`` extension on +:attr:`~gino.crud.CRUDModel.query`. This has exactly the same effect as above:: + + all_users = await User.query.gino.all() + # SQL: + # SELECT users.id, users.nickname FROM users + +.. note:: + + ``User.query`` is actually a SQLAlchemy query, with its own + non-asynchronous execution methods. GINO added this ``gino`` extension on + all executable SQLAlchemy clause objects to conveniently execute them in + the asynchronous way, so that it is even not needed to import the ``db`` + reference for execution. + +Now let's add some filters. For example, find all users with ID lower than 10:: + + founding_users = await User.query.where(User.id < 10).gino.all() + # SQL (parameter: 10): + # SELECT users.id, users.nickname FROM users WHERE users.id < $1 + +Read more `here `__ +about writing queries, because the query object is exactly from SQLAlchemy core. + +.. warning:: + + Once you get a model object, it is purely in memory and fully detached from + the database. That means, if the row is externally updated, the object + values remain unchanged. Likewise, changes made to the object won't affect + the database values. + + Also, GINO keeps no track of model objects, therefore getting the same row + twice returns two different object with identical values. Modifying one + does not magically affect the other one. + + Different than traditional ORMs, the GINO model objects are more like + objective SQL results, rather than stateful ORM objects. In order to adapt + for asynchronous programming, GINO is designed to be that simple. That's + also why GINO Is Not ORM. + +Sometimes we want to get only one object, for example getting the user by name +when logging in. There's a shortcut for this scenario:: + + user = await User.query.where(User.nickname == 'fantix').gino.first() + # SQL (parameter: 'fantix'): + # SELECT users.id, users.nickname FROM users WHERE users.nickname = $1 + +If there is no user named "fantix" in database, ``user`` will be ``None``. + +And sometimes we may want to get a single value from database, getting the name +of user with ID 1 for example. Then we can use the +:meth:`~gino.crud.CRUDModel.select` class method:: + + name = await User.select('nickname').where(User.id == 1).gino.scalar() + # SQL (parameter: 1): + # SELECT users.nickname FROM users WHERE users.id = $1 + print(name) # fantix + +Or get the count of all users:: + + population = await db.func.count(User.id).gino.scalar() + # SQL: + # SELECT count(users.id) AS count_1 FROM users + print(population) # 17 for example + + +Update +^^^^^^ + +Then let's try to make some modifications. In this example we'll mixin some +retrieve operations we just tried. :: + + # create a new user + user = await User.create(nickname='fantix') + + # get its name + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + assert name == user.nickname # they are both 'fantix' before the update + + # modification here + await user.update(nickname='daisy').apply() + # SQL (parameters: 'daisy', 1): + # UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname + print(user.nickname) # daisy + + # get its name again + name = await User.select('nickname').where( + User.id == user.id).gino.scalar() + print(name) # daisy + assert name == user.nickname # they are both 'daisy' after the update + +So :meth:`~gino.crud.CRUDModel.update` is the first GINO method we met so far +on model instance level. It accepts multiple keyword arguments, whose keys are +column names while values are the new value to update to. The following +:meth:`~gino.crud.UpdateRequest.apply` call makes the update happen in database. + +.. note:: + + GINO explicitly split the in-memory update and SQL update into two methods: + :meth:`~gino.crud.CRUDModel.update` and + :meth:`~gino.crud.UpdateRequest.apply`. :meth:`~gino.crud.CRUDModel.update` + will update the in-memory model object and return an + :class:`~gino.crud.UpdateRequest` object which contains all the + modifications. A following :meth:`~gino.crud.UpdateRequest.apply` on + :class:`~gino.crud.UpdateRequest` object will apply these recorded + modifications to database by executing a compiled SQL. + +.. tip:: + + :class:`~gino.crud.UpdateRequest` object has another method named + :meth:`~gino.crud.UpdateRequest.update` which works the same as the one + on model object, just that it combines the new modifications together with + the ones already recorded in current :class:`~gino.crud.UpdateRequest` + object, and it returns the same :class:`~gino.crud.UpdateRequest` object. + That means, you can chain the updates and end up with one + :meth:`~gino.crud.UpdateRequest.apply`, or make use of the + :class:`~gino.crud.UpdateRequest` object to combine several updates in a + batch. + +:meth:`~gino.crud.CRUDModel.update` on model object affects only the row +represented by the object. If you want to do update with wider condition, you +can use the :meth:`~gino.crud.CRUDModel.update` on model class level, with a +bit difference:: + + await User.update.values(nickname='Founding Member ' + User.nickname).where( + User.id < 10).gino.status() + # SQL (parameter: 'Founding Member ', 10): + # UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2 + + name = await User.select('nickname').where( + User.id == 1).gino.scalar() + print(name) # Founding Member fantix + +There is no :class:`~gino.crud.UpdateRequest` here, everything is again +SQLAlchemy clause, its +`documentation `_ here for +your reference. + + +Delete +^^^^^^ + +At last. Deleting is similar to updating, but way simpler. :: + + + user = await User.create(nickname='fantix') + await user.delete() + # SQL (parameter: 1): + # DELETE FROM users WHERE users.id = $1 + print(await User.get(user.id)) # None + +.. hint:: + + Remember the model object is in memory? In the last :func:`print` + statement, even though the row is already deleted in database, the object + ``user`` still exists with its values untouched. + +Or mass deletion (never forget the where clause, unless you want to truncate +the whole table!!):: + + await User.delete.where(User.id > 10).gino.status() + # SQL (parameter: 10): + # DELETE FROM users WHERE users.id > $1 + + +With basic :doc:`/how-to/crud`, you can already make some amazing stuff with +GINO. This tutorial ends here, please find out more in detail from the rest of +this documentation, and have fun hacking! diff --git a/docs/zh/master/_static/basic.css b/docs/zh/master/_static/basic.css new file mode 100644 index 0000000..b04360d --- /dev/null +++ b/docs/zh/master/_static/basic.css @@ -0,0 +1,768 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > p:first-child, +td > p:first-child { + margin-top: 0px; +} + +th > p:last-child, +td > p:last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +li > p:first-child { + margin-top: 0px; +} + +li > p:last-child { + margin-bottom: 0px; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > p:first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/zh/master/_static/css/badge_only.css b/docs/zh/master/_static/css/badge_only.css new file mode 100644 index 0000000..3c33cef --- /dev/null +++ b/docs/zh/master/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} diff --git a/docs/zh/master/_static/css/gino.css b/docs/zh/master/_static/css/gino.css new file mode 100644 index 0000000..7f0f40b --- /dev/null +++ b/docs/zh/master/_static/css/gino.css @@ -0,0 +1,826 @@ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: 100%; + box-sizing: border-box; + font-family: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', + Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; +} + +body { + background-color: #F0F0F0; + font-weight: 500; +} + +a { + color: #3E6CDE; +} + +code, kbd, samp { + font-family: monospace; + background-color: #f8f8f8; + border: 1px solid #f0f0f0; + padding: 0 4px; + border-radius: 2px; +} + +.highlight .hll { + display: block; +} + +strong { + font-weight: 700; +} + +:root { + --v-primary-base: #212B73; + --v-secondary-base: #AD755C; + --v-accent-base: #EEF5FF; + --v-error: #FF5252; + --v-info: #BDC8F1; + --v-info-light: #3E6CDE; + --v-success: #4CAF50; + --v-success-light: rgba(76, 175, 80, 0.2); + --v-warning: #FFC107; + --v-warning-light: rgba(255, 193, 7, 0.2); + --v-border: #444b54; + --v-border-light: #c0cee1; +} + +nav { + background-color: rgba(33, 43, 115, 0.95); + -webkit-box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12); +} + +.nav-wrapper { + padding: 4px 16px; + display: flex; + align-items: center; +} + +nav .brand-logo { + position: relative; + display: flex; + align-items: center; + left: 0; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; +} + +.breadcrumbs { + display: flex; + flex-wrap: nowrap; + overflow-x: scroll; + -ms-overflow-style: none; +} + +.breadcrumbs::-webkit-scrollbar { + display: none; +} + +.breadcrumbs .breadcrumb, +.breadcrumbs .breadcrumb::before { + content: '>'; + font-size: 16px; + white-space: nowrap; +} + + +.btn-flat { + display: flex; + align-items: center; + font-weight: 500; + margin-left: 22px; + text-decoration: none; + color: #fff; + padding: 14px; + font-size: 14px; + -webkit-transition: .2s; + transition: .2s; +} + +.btn-flat:hover { + color: #f8d230; + text-shadow: #f8d230 0 0 5px; +} + +a.brackets:before, +span.brackets:empty, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +a.footnote-reference { + vertical-align: super; + font-size: 0.8em; +} + +.footnote dt { + float: left; +} + +div.search { + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +} + +div.search i { + position: absolute; + left: 10px; + transition: 0.3s; +} + +#search:focus + i { + color: var(--v-primary-base); +} + +#search { + flex: 0.1 1 192px; + background-color: rgba(255, 255, 255, 0.16); + border-radius: 28px; + height: 38px; + padding: 0 32px 0 42px; + margin: 0; + border: none; + transition: 0.3s; + color: rgba(255, 255, 255, 0.32); + font-size: 14px; +} + +#search:focus { + border: none; + background-color: #FFFFFF; + color: #000000; +} + +#search-results { + position: absolute; + left: 0; + right: -250px; + max-height: 61.8vh; + background-color: rgba(0, 0, 0, 0.8); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + overflow: scroll; + top: 51px; + padding: 16px; +} + +#search-results li { + float: none; + line-height: 1.5; + font-size: 14px; +} + +#search-results li a { + color: #4E7CEE; + padding: 0; +} + +#search-results li .context { + padding: 0 0 24px 24px; +} +#search-results .highlighted { + color: #f8d230; +} + +.theme-dark { + color: #FFFFFF; +} + +.spacer { + flex-grow: 1; +} + +.img { + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.sidenav { + top: 64px; + z-index: 900; + box-shadow: none; + bottom: 0; + height: auto; +} + +.col.body { + padding: 0; +} + +#main-content { + padding: 0 24px; + background-color: white; +} + +.sidenav ul li:first-of-type { + display: none; +} + +.sidenav ul ul li:first-of-type { + display: block; +} + +.sidenav .caption { + font-size: 1.2em; + color: var(--v-primary-base); + padding: 16px 32px 0 48px; + position: relative; + font-weight: 600; +} + +.sidenav .caption::before { + position: absolute; + display: block; + content: " "; + width: 20px; + height: 20px; + left: 16px; + top: 20px; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.sidenav li > a { + font-size: 14px; + line-height: 14px; + padding: 12px 24px 12px 48px; + height: auto; +} + +.sidenav li li > a { + padding-left: 70px; +} + +.sidenav .caption:nth-of-type(1)::before { + background-image: url(../images/tutorials-icon.svg); +} +.sidenav .caption:nth-of-type(2)::before { + background-image: url(../images/how-to-icon.svg); +} +.sidenav .caption:nth-of-type(3)::before { + background-image: url(../images/explanation-logo.svg); +} +.sidenav .caption:nth-of-type(4)::before { + background-image: url(../images/reference-logo.svg); +} + +.sidenav a.current { + background-color: var(--v-accent-base); +} + +.github { + margin-left: 16px; + transition: 0.3s; +} + +.github:hover { + text-shadow: #FFFFFF 0 0 5px; +} + +.github i { + font-size: 40px; +} + +.right-nav.col { + padding: 0; +} + +.table-of-contents { + padding: 16px; + top: 64px; + bottom: 0; + overflow-y: auto; +} + +.table-of-contents li { + padding: 0; +} + +.table-of-contents > ul > li > a { + display: none; +} + +.table-of-contents > ul ul > li > a.active, +.table-of-contents > ul ul > li > a:hover { + padding-left: 14px; +} + +.table-of-contents > ul ul ul > li > a.active, +.table-of-contents > ul ul ul > li > a:hover { + padding-left: 30px; +} + +.table-of-contents > ul ul ul > li > a { + padding-left: 32px; +} + +.table-of-contents > ul ul ul ul > li > a.active, +.table-of-contents > ul ul ul ul > li > a:hover { + padding-left: 46px; +} + +.table-of-contents > ul ul ul ul > li > a { + padding-left: 48px; +} + +.table-of-contents a { + height: auto; + padding-top: 2px; + padding-bottom: 2px; +} + +.table-of-contents a.active, +.table-of-contents a:hover { + color: var(--v-primary-base); + border-left: 2px solid var(--v-primary-base); + font-weight: 500; +} + +.table-of-contents #version-selector hr { + margin-top: 48px; +} + +.table-of-contents #version-selector div { + margin-top: 16px; + color: #757575; +} + +.table-of-contents #version-selector a { + color: #757575; +} + +.table-of-contents .add-your-lang, +.table-of-contents .add-your-lang a, +.table-of-contents .add-your-lang a:active, +.table-of-contents .add-your-lang a:hover { + border-left: none; + font-size: 14px; + text-decoration: underline; + text-align: right; + margin-top: 8px; + color: #aaa; + font-weight: normal; +} + +.table-of-contents #version-selector span, +.table-of-contents #version-selector a, +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + border-left: none; + padding: 8px; + margin-left: 16px; +} + +.table-of-contents #version-selector a:active, +.table-of-contents #version-selector a:hover { + color: var(--v-info-light); +} + +.table-of-contents #version-selector span { + color: var(--v-info-light); +} + +.version-warning { + border: 1px solid #757575; + background-color: #ffaaaa; + padding: 8px; +} + +h1 { + font-size: 32px; + margin: 32px 0 20px 0; + font-weight: 600; +} + +h2 { + font-size: 26px; + margin: 18px 0 12px 0; + font-weight: 600; +} + +h3 { + font-size: 22px; + margin: 17px 0 11px 0; + font-weight: 600; +} + +h4 { + font-size: 18px; + margin: 14px 0 9px 0; + font-weight: 600; +} + +h5 { + font-size: 16px; + margin: 13px 0 8px 0; + font-weight: 600; +} + +h6 { + font-size: 15px; + margin: 12px 0 7px 0; + font-weight: 600; +} + +a.headerlink { + padding-left: 8px; + visibility: hidden; + position: absolute; + font-family: sans-serif; +} + +:hover > a.headerlink { + visibility: visible; +} + +.section { + /*margin-top: -64px;*/ + /*padding-top: 64px;*/ +} + +.highlight { + background-color: rgba(0, 0, 0, 0.8); + clear: both; +} + +.highlight pre { + border: 1px solid var(--v-border-light); + padding: 1em; + font-size: 14px; + overflow-x: auto; +} + +.body li p { + margin: 0; +} + +.body ul li { + list-style-type: disc; + margin-left: 2rem; +} + +.body ul li li { + list-style-type: circle; +} + +.body ul li li li { + list-style-type: square; +} + +.admonition { + border-radius: 4px; + padding: 1px 1rem 40px; + box-shadow: #D1CECE 0 0 4px; + background-repeat: no-repeat; + background-position: 100% calc(100% + 20px); + background-size: 128px; +} + +.admonition-title { + font-size: 18px; + font-weight: 600; + color: #3E6CDE; +} + +.admonition-title:before { + content: " "; + display: inline-block; + width: 20px; + height: 20px; + margin: 0 8px -4px 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +.admonition.note, +.admonition.seealso { + background-color: #F5FAFD; + background-image: url(../images/hmm.png); +} + +.admonition.note .admonition-title:before, +.admonition.seealso .admonition-title:before { + background-image: url(../images/icon-note.svg); +} + +.admonition.tip, +.admonition.hint { + background-color: #F5FDF6; + background-image: url(../images/aha.png); +} + +.admonition.tip { + background-image: url(../images/OK.png); +} + +.admonition.hint .admonition-title:before { + background-image: url(../images/icon-hint.svg); +} + +.admonition.warning { + background-color: #FDF5F5; + background-image: url(../images/fighting.png); +} + +.admonition.warning .admonition-title:before { + background-image: url(../images/icon-warning.svg); +} + +.admonition.tip .admonition-title:before { + background-image: url(../images/icon-info.svg); +} + +.body .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 700px; +} + +.body .section ul.boxed-nav li { + border-radius: 4px; + box-shadow: #D1CECE 0 0 4px; + padding: 10px; + margin: 25px 0 0; + width: 340px; + list-style: none; + color: #202A73; + font-weight: 500; + overflow: hidden; +} + +.body .section ul.boxed-nav li div { + background-color: #F4F7FC; + border-radius: 4px; + height: 110px; + padding: 30px 30px 0; + background-image: url(../images/box-bg-dec.svg); + background-repeat: no-repeat; + background-position: -15px -10px; +} + +.body .section ul.boxed-nav li:nth-of-type(3n) div { + background-image: url(../images/box-bg-dec-2.svg); +} + +.body .section ul.boxed-nav li:nth-of-type(3n+1) div { + background-image: url(../images/box-bg-dec-3.svg); +} + +.body .section ul.boxed-nav li a img { + width: 42px; + margin: 0 30px 30px 0; + float: left; +} + +.body .section ul.boxed-nav li p { + font-size: 18px; + color: #212B73; + line-height: 18px; + margin-bottom: 8px; +} + +.body .section ul.boxed-nav li p:nth-of-type(2) { + font-size: 14px; + word-break: break-word; +} + +.body .section ul.boxed-nav p a, +.body .section ul.boxed-nav p a:visited { + color: #212B73; +} + +p.divio { + position: absolute; + font-size: 0.8em; + color: #aaa; + margin-top: 40px; +} + +p.divio a, p.divio a:visited { + color: #aaa; +} + +.github-stars { + display: flex; + align-items: center; + padding: 0; + margin-left: 32px; + border-radius: 27px; + font-size: 14px; + font-weight: 500; +} + +.github-stars div { + border: 1px solid #fff; + line-height: 27px; + background-color: #1E2B78; + transition: 0.1s; + height: 29px; +} + +.github-stars div.left { + border-bottom-left-radius: 27px; + border-top-left-radius: 27px; + border-right: none; + padding: 0 7px 0 34px; + position: relative; +} + +.github-stars div.left .github-logo { + position: absolute; + height: 29px; + top: -1px; + left: -1px; +} + +.github-stars div.right { + border-bottom-right-radius: 27px; + border-top-right-radius: 27px; + padding: 0 7px 0 7px; + display: flex; + align-items: center; +} + +.github-stars div.right img { + height: 14px; + margin-right: 7px; +} + +.github-stars:hover { + color: white; + text-shadow: none; + box-shadow: #ffffff 0 0 5px; +} + +.btn-large.white img { + width: 32px; + height: 32px; + margin-top: 6px; +} + +.btn-large.white div { + color: #3E6CDE; + position: absolute; + bottom: 0; + width: 56px; + line-height: 18px; + font-size: 14px; + font-weight: 600; +} + +.sidenav-overlay { + z-index: 897; +} + +nav a.sidenav-trigger { + height: 24px; + margin: 16px 16px 16px 0; + display: flex; + flex-direction: column; + justify-content: space-around; +} +nav a.sidenav-trigger hr { + width: 24px; + margin: 0; + border: 0; + background-color: #fff; + height: 2px; +} + +.fixed-action-btn { + display: none; +} + +@media (min-width: 993px) { + main { + padding-left: 300px; + } + + #main-content { + padding: 0 48px; + margin: 16px; + border-radius: 4px; + } + + .admonition { + margin: 1.5rem 2rem; + padding: 1px 3rem 40px; + } + + img.align-center { + max-width: 100%; + display: block; + margin: 0 auto; + } + + img.align-left { + max-width: 100%; + float: left; + margin: 0 2rem 1.5rem 0; + } + + img.align-right { + max-width: 100%; + float: right; + margin: 0 0 1.5rem 2rem; + } + + .table-of-contents { + width: calc(25vw - 75px); + } + + p.divio { + margin-left: -47px; + } + + .btn-flat { + margin-left: 22px; + padding: 14px; + } + + .sidenav { + top: 64px; + } + + nav a.sidenav-trigger { + display: none; + } +} + +@media (max-width: 992px) { + img.align-left, + img.align-center, + img.align-right { + display: block; + margin: 1.5rem auto; + max-width: 100%; + } + + .admonition { + overflow: scroll; + } + + #main-content { + padding-bottom: 56px; + } + + #main-content p { + overflow-x: scroll; + } + + main > .row { + margin-bottom: 0; + } + + .breadcrumbs { + display: none; + } + + .btn-flat { + margin-left: 10px; + padding: 5px; + } +} + +@media (max-width: 740px) { + #search-container { + display: none; + } +} + +@media (max-width: 600px) { + .sidenav { + top: 56px; + } + + .fixed-action-btn { + display: block; + } +} + +@media (max-width: 510px) { + .github-stars { + display: none; + } +} diff --git a/docs/zh/master/_static/css/materialize.min.css b/docs/zh/master/_static/css/materialize.min.css new file mode 100644 index 0000000..74b1741 --- /dev/null +++ b/docs/zh/master/_static/css/materialize.min.css @@ -0,0 +1,13 @@ +/*! + * Materialize v1.0.0 (http://materializecss.com) + * Copyright 2014-2017 Materialize + * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) + */ +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/docs/zh/master/_static/css/theme.css b/docs/zh/master/_static/css/theme.css new file mode 100644 index 0000000..aed8cef --- /dev/null +++ b/docs/zh/master/_static/css/theme.css @@ -0,0 +1,6 @@ +/* sphinx_rtd_theme version 0.4.3 | MIT license */ +/* Built 20190212 16:02 */ +*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,.rst-content code,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:.5cm}p,h2,.rst-content .toctree-wrapper p.caption,h3{orphans:3;widows:3}h2,.rst-content .toctree-wrapper p.caption,h3{page-break-after:avoid}}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content .code-block-caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.rst-content .admonition,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.7.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),url("../fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content .code-block-caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.2857142857em;text-align:center}.fa-ul{padding-left:0;margin-left:2.1428571429em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.1428571429em;width:2.1428571429em;top:.1428571429em;text-align:center}.fa-li.fa-lg{left:-1.8571428571em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.wy-menu-vertical li span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.rst-content .fa-pull-left.admonition-title,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content dl dt .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.rst-content code.download span.fa-pull-left:first-child,.fa-pull-left.icon{margin-right:.3em}.fa.fa-pull-right,.wy-menu-vertical li span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.rst-content .fa-pull-right.admonition-title,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content dl dt .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.rst-content code.download span.fa-pull-right:first-child,.fa-pull-right.icon{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.wy-menu-vertical li span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content .code-block-caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.rst-content code.download span.pull-left:first-child,.pull-left.icon{margin-right:.3em}.fa.pull-right,.wy-menu-vertical li span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content .code-block-caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.rst-content code.download span.pull-right:first-child,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-remove:before,.fa-close:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-hotel:before,.fa-bed:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-yc:before,.fa-y-combinator:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-tv:before,.fa-television:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:""}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-signing:before,.fa-sign-language:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-vcard:before,.fa-address-card:before{content:""}.fa-vcard-o:before,.fa-address-card-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content .code-block-caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content .code-block-caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .rst-content p.caption .headerlink,.rst-content p.caption a .headerlink,a .rst-content table>caption .headerlink,.rst-content table>caption a .headerlink,a .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption a .headerlink,a .rst-content tt.download span:first-child,.rst-content tt.download a span:first-child,a .rst-content code.download span:first-child,.rst-content code.download a span:first-child,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .btn span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.btn .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .rst-content p.caption .headerlink,.rst-content p.caption .btn .headerlink,.btn .rst-content table>caption .headerlink,.rst-content table>caption .btn .headerlink,.btn .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption .btn .headerlink,.btn .rst-content tt.download span:first-child,.rst-content tt.download .btn span:first-child,.btn .rst-content code.download span:first-child,.rst-content code.download .btn span:first-child,.btn .icon,.nav .fa,.nav .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand,.nav .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .rst-content p.caption .headerlink,.rst-content p.caption .nav .headerlink,.nav .rst-content table>caption .headerlink,.rst-content table>caption .nav .headerlink,.nav .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption .nav .headerlink,.nav .rst-content tt.download span:first-child,.rst-content tt.download .nav span:first-child,.nav .rst-content code.download span:first-child,.rst-content code.download .nav span:first-child,.nav .icon{display:inline}.btn .fa.fa-large,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.btn .rst-content .code-block-caption .fa-large.headerlink,.rst-content .code-block-caption .btn .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .btn span.fa-large:first-child,.btn .rst-content code.download span.fa-large:first-child,.rst-content code.download .btn span.fa-large:first-child,.btn .fa-large.icon,.nav .fa.fa-large,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.nav .rst-content .code-block-caption .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.nav .rst-content code.download span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.nav .fa-large.icon{line-height:.9em}.btn .fa.fa-spin,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.btn .rst-content .code-block-caption .fa-spin.headerlink,.rst-content .code-block-caption .btn .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .btn span.fa-spin:first-child,.btn .rst-content code.download span.fa-spin:first-child,.rst-content code.download .btn span.fa-spin:first-child,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.nav .rst-content .code-block-caption .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.nav .rst-content code.download span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.wy-menu-vertical li span.btn.toctree-expand:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.rst-content code.download span.btn:first-child:before,.btn.icon:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.rst-content code.download span.btn:first-child:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.rst-content tt.download .btn-mini span:first-child:before,.btn-mini .rst-content code.download span:first-child:before,.rst-content code.download .btn-mini span:first-child:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.rst-content .admonition{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.admonition{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo,.rst-content .wy-alert-warning.admonition{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title,.rst-content .wy-alert-warning.admonition .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.admonition{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.admonition{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.admonition{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 .3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.3576515979%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.3576515979%;width:48.821174201%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.3576515979%;width:31.7615656014%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type="datetime-local"]{padding:.34375em .625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type="radio"][disabled],input[type="checkbox"][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{position:absolute;content:"";display:block;left:0;top:0;width:36px;height:12px;border-radius:4px;background:#ccc;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{position:absolute;content:"";display:block;width:18px;height:18px;border-radius:4px;background:#999;left:-3px;top:-3px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27AE60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:.3em;display:block}.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,.rst-content .toctree-wrapper p.caption,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2,.rst-content .toctree-wrapper p.caption{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt,.rst-content code{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:before,.wy-breadcrumbs:after{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs li code,.wy-breadcrumbs li .rst-content tt,.rst-content .wy-breadcrumbs li tt{padding:5px;border:none;background:none}.wy-breadcrumbs li code.literal,.wy-breadcrumbs li .rst-content tt.literal,.rst-content .wy-breadcrumbs li tt.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#3a7ca8;height:32px;display:inline-block;line-height:32px;padding:0 1.618em;margin:12px 0 0 0;display:block;font-weight:bold;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li code,.wy-menu-vertical li .rst-content tt,.rst-content .wy-menu-vertical li tt{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.on a:hover span.toctree-expand,.wy-menu-vertical li.current>a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a{color:#404040}.wy-menu-vertical li.toctree-l1.current li.toctree-l2>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>ul{display:none}.wy-menu-vertical li.toctree-l1.current li.toctree-l2.current>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>ul{display:block}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{display:block;background:#c9c9c9;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3{font-size:.9em}.wy-menu-vertical li.toctree-l3.current>a{background:#bdbdbd;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{display:block;background:#bdbdbd;padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980B9;text-align:center;padding:.809em;display:block;color:#fcfcfc;margin-bottom:.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-side-nav-search>a img.logo,.wy-side-nav-search .wy-dropdown>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search>a.icon img.logo,.wy-side-nav-search .wy-dropdown>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:normal;color:rgba(255,255,255,0.3)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:gray}footer p{margin-bottom:12px}footer span.commit code,footer span.commit .rst-content tt,.rst-content footer span.commit tt{padding:0px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;font-size:1em;background:none;border:none;color:gray}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{width:100%}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:before,.rst-breadcrumbs-buttons:after{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-side-scroll{width:auto}.wy-side-nav-search{width:auto}.wy-menu.wy-menu-vertical{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1100px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px 12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;display:block;overflow:auto}.rst-content pre.literal-block,.rst-content div[class^='highlight']{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px 0}.rst-content pre.literal-block div[class^='highlight'],.rst-content div[class^='highlight'] div[class^='highlight']{padding:0px;border:none;margin:0}.rst-content div[class^='highlight'] td.code{width:100%}.rst-content .linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;display:block;overflow:auto}.rst-content div[class^='highlight'] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content pre.literal-block,.rst-content div[class^='highlight'] pre,.rst-content .linenodiv pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;font-size:12px;line-height:1.4}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^='highlight'],.rst-content div[class^='highlight'] pre{white-space:pre-wrap}}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last,.rst-content .admonition .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .section ol p:last-child,.rst-content .section ul p:last-child{margin-bottom:24px}.rst-content .line-block{margin-left:0px;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content .toctree-wrapper p.caption .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content .code-block-caption .headerlink{visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content .toctree-wrapper p.caption .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after,.rst-content .code-block-caption .headerlink:after{content:"";font-family:FontAwesome}.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content .toctree-wrapper p.caption:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after,.rst-content .code-block-caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:baseline;position:relative;top:-0.4em;line-height:0;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:gray}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.docutils.citation tt,.rst-content table.docutils.citation code,.rst-content table.docutils.footnote tt,.rst-content table.docutils.footnote code{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}.rst-content table.docutils td .last,.rst-content table.docutils td .last :last-child{margin-bottom:0}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content tt,.rst-content tt,.rst-content code{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;padding:2px 5px}.rst-content tt big,.rst-content tt em,.rst-content tt big,.rst-content code big,.rst-content tt em,.rst-content code em{font-size:100% !important;line-height:normal}.rst-content tt.literal,.rst-content tt.literal,.rst-content code.literal{color:#E74C3C}.rst-content tt.xref,a .rst-content tt,.rst-content tt.xref,.rst-content code.xref,a .rst-content tt,a .rst-content code{font-weight:bold;color:#404040}.rst-content pre,.rst-content kbd,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace}.rst-content a tt,.rst-content a tt,.rst-content a code{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold;margin-bottom:12px}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:#555}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) code{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) code.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}.rst-content tt.download,.rst-content code.download{background:inherit;padding:inherit;font-weight:normal;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content tt.download span:first-child,.rst-content code.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-regular.eot");src:url("../fonts/Lato/lato-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-regular.woff2") format("woff2"),url("../fonts/Lato/lato-regular.woff") format("woff"),url("../fonts/Lato/lato-regular.ttf") format("truetype");font-weight:400;font-style:normal}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-bold.eot");src:url("../fonts/Lato/lato-bold.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-bold.woff2") format("woff2"),url("../fonts/Lato/lato-bold.woff") format("woff"),url("../fonts/Lato/lato-bold.ttf") format("truetype");font-weight:700;font-style:normal}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-bolditalic.eot");src:url("../fonts/Lato/lato-bolditalic.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-bolditalic.woff2") format("woff2"),url("../fonts/Lato/lato-bolditalic.woff") format("woff"),url("../fonts/Lato/lato-bolditalic.ttf") format("truetype");font-weight:700;font-style:italic}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-italic.eot");src:url("../fonts/Lato/lato-italic.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-italic.woff2") format("woff2"),url("../fonts/Lato/lato-italic.woff") format("woff"),url("../fonts/Lato/lato-italic.ttf") format("truetype");font-weight:400;font-style:italic}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:400;src:url("../fonts/RobotoSlab/roboto-slab.eot");src:url("../fonts/RobotoSlab/roboto-slab-v7-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.woff2") format("woff2"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.woff") format("woff"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.ttf") format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:700;src:url("../fonts/RobotoSlab/roboto-slab-v7-bold.eot");src:url("../fonts/RobotoSlab/roboto-slab-v7-bold.eot?#iefix") format("embedded-opentype"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.woff2") format("woff2"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.woff") format("woff"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.ttf") format("truetype")} diff --git a/docs/zh/master/_static/doctools.js b/docs/zh/master/_static/doctools.js new file mode 100644 index 0000000..b33f87f --- /dev/null +++ b/docs/zh/master/_static/doctools.js @@ -0,0 +1,314 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/zh/master/_static/documentation_options.js b/docs/zh/master/_static/documentation_options.js new file mode 100644 index 0000000..2638fe0 --- /dev/null +++ b/docs/zh/master/_static/documentation_options.js @@ -0,0 +1,11 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.0.0a0', + LANGUAGE: 'zh', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/zh/master/_static/favicon.ico b/docs/zh/master/_static/favicon.ico new file mode 100644 index 0000000..07b38bc Binary files /dev/null and b/docs/zh/master/_static/favicon.ico differ diff --git a/docs/zh/master/_static/file.png b/docs/zh/master/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/docs/zh/master/_static/file.png differ diff --git a/docs/zh/master/_static/fonts/Inconsolata-Bold.ttf b/docs/zh/master/_static/fonts/Inconsolata-Bold.ttf new file mode 100644 index 0000000..809c1f5 Binary files /dev/null and b/docs/zh/master/_static/fonts/Inconsolata-Bold.ttf differ diff --git a/docs/zh/master/_static/fonts/Inconsolata-Regular.ttf b/docs/zh/master/_static/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000..fc981ce Binary files /dev/null and b/docs/zh/master/_static/fonts/Inconsolata-Regular.ttf differ diff --git a/docs/zh/master/_static/fonts/Inconsolata.ttf b/docs/zh/master/_static/fonts/Inconsolata.ttf new file mode 100644 index 0000000..4b8a36d Binary files /dev/null and b/docs/zh/master/_static/fonts/Inconsolata.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato-Bold.ttf b/docs/zh/master/_static/fonts/Lato-Bold.ttf new file mode 100644 index 0000000..1d23c70 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato-Bold.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato-Regular.ttf b/docs/zh/master/_static/fonts/Lato-Regular.ttf new file mode 100644 index 0000000..0f3d0f8 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato-Regular.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bold.eot b/docs/zh/master/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 0000000..3361183 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bold.ttf b/docs/zh/master/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 0000000..29f691d Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bold.woff b/docs/zh/master/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bold.woff2 b/docs/zh/master/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bolditalic.eot b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 0000000..3d41549 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bolditalic.ttf b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 0000000..f402040 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bolditalic.woff b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-italic.eot b/docs/zh/master/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 0000000..3f82642 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-italic.ttf b/docs/zh/master/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 0000000..b4bfc9b Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-italic.woff b/docs/zh/master/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-italic.woff2 b/docs/zh/master/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-regular.eot b/docs/zh/master/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 0000000..11e3f2a Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-regular.ttf b/docs/zh/master/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 0000000..74decd9 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-regular.woff b/docs/zh/master/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/zh/master/_static/fonts/Lato/lato-regular.woff2 b/docs/zh/master/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/docs/zh/master/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab-Bold.ttf b/docs/zh/master/_static/fonts/RobotoSlab-Bold.ttf new file mode 100644 index 0000000..df5d1df Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab-Bold.ttf differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab-Regular.ttf b/docs/zh/master/_static/fonts/RobotoSlab-Regular.ttf new file mode 100644 index 0000000..eb52a79 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab-Regular.ttf differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 0000000..79dc8ef Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 0000000..df5d1df Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 0000000..2f7ca78 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 0000000..eb52a79 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/docs/zh/master/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/zh/master/_static/fonts/fontawesome-webfont.eot b/docs/zh/master/_static/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/docs/zh/master/_static/fonts/fontawesome-webfont.eot differ diff --git a/docs/zh/master/_static/fonts/fontawesome-webfont.svg b/docs/zh/master/_static/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/docs/zh/master/_static/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/zh/master/_static/fonts/fontawesome-webfont.ttf b/docs/zh/master/_static/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/docs/zh/master/_static/fonts/fontawesome-webfont.ttf differ diff --git a/docs/zh/master/_static/fonts/fontawesome-webfont.woff b/docs/zh/master/_static/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/docs/zh/master/_static/fonts/fontawesome-webfont.woff differ diff --git a/docs/zh/master/_static/fonts/fontawesome-webfont.woff2 b/docs/zh/master/_static/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/docs/zh/master/_static/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/zh/master/_static/gino.css b/docs/zh/master/_static/gino.css new file mode 100644 index 0000000..22879a4 --- /dev/null +++ b/docs/zh/master/_static/gino.css @@ -0,0 +1,109 @@ + +.rst-content .section ul.boxed-nav { + list-style: none; + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.rst-content .section ul.boxed-nav li { + border-radius: 16px; + background-color: rgba(0, 0, 128, 0.05); + padding: 16px; + margin: 32px 0 0; + width: 330px; + list-style: none; + display: flex; + flex-direction: column; + align-items: center; + color: #777; +} + +.rst-content .section ul.boxed-nav p:first-of-type { + margin-top: 5px; + font-size: 1.3em; + color: #000; +} + +.rst-content .section ul.boxed-nav li a, +.rst-content .section ul.boxed-nav li a:visited { + color: #777; +} + +.rst-content .section ul.boxed-nav p:first-of-type a, +.rst-content .section ul.boxed-nav p:first-of-type a:visited { + color: #000; +} + +.rst-content .section ul.boxed-nav li:nth-of-type(5), +.rst-content .section ul.boxed-nav li:nth-of-type(6), +.rst-content .section ul.boxed-nav li:nth-of-type(7), +.rst-content .section ul.boxed-nav li:nth-of-type(8) { + background-color: rgba(128, 0, 0, 0.05); +} + +p.icons8 { + position: absolute; + padding-top: 32px; + font-size: 0.8em; + color: #aaa; +} + +p.icons8 a, p.icons8 a:visited { + color: #aaa; +} + +@media (min-width: 1200px) { + div.document { + position: relative; + } + + .contents { + position: fixed; + overflow-y: auto; + left: 1100px; + top: 0; + width: calc(100vw - 1100px); + bottom: 0; + padding: 0 1.618em; + } + + .contents a, .contents a:visited { + color: #2980B9; + } + + .contents p.topic-title { + margin-top: calc(49px + 29px + 1.618em); + } + + .rst-content .section .contents > ul > li { + list-style: none; + margin: 0; + } + + .contents > ul > li > p { + display: none; + } + + .rst-content .section .contents ul p { + margin: 0; + } + + .rst-content .section .contents ul li li { + list-style: disc; + } + + .rst-content .section .contents ul li li li { + list-style: circle; + } + + .rst-content .section .contents ul li li li li { + list-style: square; + } +} + +@media (max-width: 1300px) { + .contents { + display: none; + } +} diff --git a/docs/zh/master/_static/images/OK.png b/docs/zh/master/_static/images/OK.png new file mode 100644 index 0000000..81f5433 Binary files /dev/null and b/docs/zh/master/_static/images/OK.png differ diff --git a/docs/zh/master/_static/images/aha.png b/docs/zh/master/_static/images/aha.png new file mode 100644 index 0000000..a421799 Binary files /dev/null and b/docs/zh/master/_static/images/aha.png differ diff --git a/docs/zh/master/_static/images/box-bg-dec-2.svg b/docs/zh/master/_static/images/box-bg-dec-2.svg new file mode 100644 index 0000000..0c1e246 --- /dev/null +++ b/docs/zh/master/_static/images/box-bg-dec-2.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/zh/master/_static/images/box-bg-dec-3.svg b/docs/zh/master/_static/images/box-bg-dec-3.svg new file mode 100644 index 0000000..0d757ca --- /dev/null +++ b/docs/zh/master/_static/images/box-bg-dec-3.svg @@ -0,0 +1,25 @@ + + + + Path 10 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/docs/zh/master/_static/images/box-bg-dec.svg b/docs/zh/master/_static/images/box-bg-dec.svg new file mode 100644 index 0000000..a6e38d5 --- /dev/null +++ b/docs/zh/master/_static/images/box-bg-dec.svg @@ -0,0 +1,21 @@ + + + + Path 135 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/docs/zh/master/_static/images/explanation-logo.svg b/docs/zh/master/_static/images/explanation-logo.svg new file mode 100644 index 0000000..98df7c3 --- /dev/null +++ b/docs/zh/master/_static/images/explanation-logo.svg @@ -0,0 +1,20 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/fighting.png b/docs/zh/master/_static/images/fighting.png new file mode 100644 index 0000000..a7f6ddd Binary files /dev/null and b/docs/zh/master/_static/images/fighting.png differ diff --git a/docs/zh/master/_static/images/hmm.png b/docs/zh/master/_static/images/hmm.png new file mode 100644 index 0000000..8b60eb6 Binary files /dev/null and b/docs/zh/master/_static/images/hmm.png differ diff --git a/docs/zh/master/_static/images/how-to-icon.svg b/docs/zh/master/_static/images/how-to-icon.svg new file mode 100644 index 0000000..6506741 --- /dev/null +++ b/docs/zh/master/_static/images/how-to-icon.svg @@ -0,0 +1,24 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/icon-hint.svg b/docs/zh/master/_static/images/icon-hint.svg new file mode 100644 index 0000000..db86c44 --- /dev/null +++ b/docs/zh/master/_static/images/icon-hint.svg @@ -0,0 +1,27 @@ + + + + 编组 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/icon-info.svg b/docs/zh/master/_static/images/icon-info.svg new file mode 100644 index 0000000..c4690a0 --- /dev/null +++ b/docs/zh/master/_static/images/icon-info.svg @@ -0,0 +1,27 @@ + + + + 编组 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/icon-note.svg b/docs/zh/master/_static/images/icon-note.svg new file mode 100644 index 0000000..03308b0 --- /dev/null +++ b/docs/zh/master/_static/images/icon-note.svg @@ -0,0 +1,21 @@ + + + + 编组 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/icon-warning.svg b/docs/zh/master/_static/images/icon-warning.svg new file mode 100644 index 0000000..4e3ea02 --- /dev/null +++ b/docs/zh/master/_static/images/icon-warning.svg @@ -0,0 +1,29 @@ + + + + 编组 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/language.svg b/docs/zh/master/_static/images/language.svg new file mode 100644 index 0000000..b61c7d4 --- /dev/null +++ b/docs/zh/master/_static/images/language.svg @@ -0,0 +1,16 @@ + + + + Group 4 + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/reference-logo.svg b/docs/zh/master/_static/images/reference-logo.svg new file mode 100644 index 0000000..b79d156 --- /dev/null +++ b/docs/zh/master/_static/images/reference-logo.svg @@ -0,0 +1,22 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/images/tutorials-icon.svg b/docs/zh/master/_static/images/tutorials-icon.svg new file mode 100644 index 0000000..ff76008 --- /dev/null +++ b/docs/zh/master/_static/images/tutorials-icon.svg @@ -0,0 +1,21 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/_static/jquery-3.4.1.js b/docs/zh/master/_static/jquery-3.4.1.js new file mode 100644 index 0000000..773ad95 --- /dev/null +++ b/docs/zh/master/_static/jquery-3.4.1.js @@ -0,0 +1,10598 @@ +/*! + * jQuery JavaScript Library v3.4.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-05-01T21:04Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.4.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a global context + globalEval: function( code, options ) { + DOMEval( code, { nonce: options && options.nonce } ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.4 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2019-04-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) && + + // Support: IE 8 only + // Exclude object elements + (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && rdescend.test( selector ) ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = (elem.ownerDocument || elem).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( typeof elem.contentDocument !== "undefined" ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
    " ], + col: [ 2, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + } ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + // Support: IE 9-11 only + // Also use offsetWidth/offsetHeight for when box sizing is unreliable + // We use getClientRects() to check for hidden/disconnected. + // In those cases, the computed value can be trusted to be border-box + if ( ( !support.boxSizingReliable() && isBorderBox || + val === "auto" || + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = Date.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url, options ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/explanation/async.html b/docs/zh/master/explanation/async.html new file mode 100644 index 0000000..cda1b9f --- /dev/null +++ b/docs/zh/master/explanation/async.html @@ -0,0 +1,281 @@ + + + + + + + + 异步编程基础 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    异步编程基础

    +
    +

    一个经典场景

    +

    假如现在我们想要去创建自己的搜索引擎,为了创建索引数据库,家境贫寒的我们使用了一台单核电脑去爬取网页(I/O型操作),然后再将这些网页内容进行处理(计算型操作)。这个过程如下图所示:

    +../_images/why_single_task.png +

    由于我们有很多网页去要去进行爬取和处理,所以我们只能将上面的过程一个个顺序执行:

    +../_images/why_throughput.png +

    假设每个过程耗时一样,每 1 秒可以进行 2 次这样的处理,那么我可以称我们这台单核的机器的吞吐量为 2 任务/秒。如何提升当前系统的吞吐量?一个比较简单的做法就是增加 CPU 的核心数量:

    +../_images/why_multicore.png +

    如上图所示,我们超级加倍了 CPU 资源后,在未达到网络瓶颈的情况下,吞吐量也轻松翻倍,达到了 4 任务/秒。但是,我们还可以针对单个 CPU 核心增加吞吐量吗?当然可以,使用多线程:

    +../_images/why_multithreading.png +

    桥多麻袋!即便是有用了 2 个线程,我们的单核机器在 2 秒内勉勉强强才完成了 6 个任务,吞吐量也只有 2.7 任务/秒,远比 2 核心机器的 4 任务/秒的吞吐量少诶,所以所线程有啥问题呢?一起康康上面的图:

    +
      +
    • 图片上黄色色块代表额外的时间开销(啥是额外时间?马上就会提到)

    • +
    • 绿绿的色块(代表爬取网页任务)在上图所示的两个线程上可以重叠,代表可以被多个线程一起执行,但是

    • +
    • 非绿色的色块却不能彼此重叠,即不能被多线程同时一起执行。

    • +
    +

    黄色色块代表被上下文切换所消耗的时间,简单理解,就是在单核 CPU 并发处理时在多个线程中切换所消耗的时间。单核 CPU 只能在同一时间处理一件事(假设这个世界上类似超线程技术还没有被发明出来),所以为了在单核 CPU 上同时处理多线程,CPU 必须将自己的时间分成片,并且在这些被分好的时间片内处理一点点当前线程的任务,然后在下一个时间片切换到其他线程继续处理。所以刚才所说的黄色色块所消耗的额外时间实际上就是在这些线程直接来回切换所消耗的时间。虽然上图的黄色色块看上去好像占了好大一块,但实际上没这么夸张,这里是为了方便理解故意搞了个大新闻。

    +

    桥多麻袋!!还没完!既然绿色的色块可以在多线程图上重合,是不是代表这 CPU 同时处理了爬取网页的操作呢?答案是 NO!在绿色的色块覆盖的时间内,CPU 没有处理任何事情,这是因为它正在等待 HTTP 的响应(I/O 操作)。这也是使用了多线程后吞吐量还是提高到了 2.7 任务/秒,而不是降低到 1.7 任务/秒的原因。你可能会想尝试用单核 CPU 搞个多线程去处理计算密集型的任务,但这肯定没有任何提高,甚至会适合其反,正如上图所示的红色色块一样(代表处理爬取到信息的任务;实际中的上下文切换可能更加频繁一些),只能说这些任务看上去好像是同时在运行一样,但是这些任务交替执行所消耗的时间甚至比一个个的顺序执行所消耗的时间更多。这也就是为什么当前的事例中只能被称为并发,而不是并行。

    +

    你也许还会想,每多增加一个线程吞吐量可能还是会有所提升,直到因为上下文切换所消耗的时间过多而影响了系统的吞吐量,这里还没有算上每一个新的线程所占用的系统内存。通常我们不会在实际生产中在单核的 CPU 上去跑它个几百几千个线程。那么问题来了,有可能在单核 CPU 上并发处理数万个 I/O 密集型的任务吗?这就是著名的 C10k 问题,而解决这个问题的办法,正是异步 I/O:

    +../_images/why_coroutine.png +
    +

    注解

    +

    需要注意的是,异步 I/O 和协程是两个概念,但是他们经常被一起提及。在这里,为了简单起见,我们将只讲协程。

    +
    +

    看上去很奈斯嘛!现在的吞吐量是 3.7 任务/秒,已经快达到我们买不起的双核 CPU 机器的 4 任务/秒吞吐量了!虽然这些图都不是使用真是的数据,但是相比系统级别的线程而言,协程真的在上下文切换上开销更加少并且占用更少的内存,这也让协程成为解决 C10K 问题的理想选择,

    +
    +
    +

    协作式多任务处理

    +

    说了半天,协程到底是什么?

    +

    在上一个图中,你可能已经注意到了它和之前一张图片不一样的地方:在同一条线程内,绿色的色块彼此重叠了。这正是因为我们在上图中使用了异步 I/O 编程,而之前的图片中我们使用的是阻塞式的 I/O 编程。顾名思义,阻塞式的 I/O 编程会在代码执行到进行 I/O 操作时等待 I/O 结果返回,从而阻塞当前线程。因此,每条线程中同时只可能执行一个阻塞式的 I/O 操作。为了能够在阻塞式的 I/O 编程中达到并发,要么使用多线程,要么就使用多进程。对比之下,异步 I/O 允许在一条线程中同时进行数千(甚至更多)次 I/O 并发操作,此时在同一线程内,每一次的 I/O 操作只会阻塞当前协程而不是整个线程。和多线程一样,协程可以完成 I/O 的并发,但它仅仅只发生在一条线程内。

    +

    在操作系统中,线程是以抢占式多任务处理的方式进行的。举个栗子,还记得我们之前的图表吗,我们只有一个 CPU 核心,当两个线程准备处理各自爬取到的第一个网页的内容,此时第一个线程先执行,但是还没等线程一执行完毕,操作系统就打断了线程一转而去执行线程二,但是线程一的活还没干完啊,所以还要等到系统挂起线程二重新再执行线程一。如果线程内需要处理的工作很复杂的话,这里面的切换次数那就多了。但也正因为有这样的切换,每个线程才有可能去执行它当前的内容。请阅读下文,并回答文中使用了哪一种修辞手法:

    +
    Thread 1: I wanna run!
    +OS: Okay, here you go...
    +Thread 2: I wanna run!
    +OS: Urh, alright one sec ... Thread 1, hold on for a while!
    +Thread 1: Well I'm not done yet, but you are the boss.
    +OS: It won't be long. Thread 2 it's your turn now.
    +Thread 2: Yay! (&%#$@..+*&#)
    +Thread 1: Can I run now?
    +OS: Just a moment please ... Thread 2, give it a break!
    +Thread 2: Alright ... but I really need the CPU.
    +OS: You'll have it later. Thread 1, hurry up!
    +
    +
    +

    而协程,恰恰相反,它是在某个事件管家的帮助下合作完成任务的。这个事件管家同样也和协程在同一条线程内。但是和通过系统调度完成强制切换的线程不一样,事件管家只会在协程自我挂起的时候完成协程的切换。上面说了线程是抢占式地多任务,因此它是只要自己不阻塞了就想被运行,但是协程相对佛系,一切由事件管家调度,来决定哪一条协程可以被运行。当一条执行中的协程突然遇到一个需要等待的事件(比如HTTP响应),它会将控制权交换给事件管家然后默默等待事件完成,此时事件管家就去找去找另外一条已经得到事件响应的协程,然后将控制权交给它,让它执行。这种并发模式被称为`协作式多任务处理<https://en.wikipedia.org/wiki/Cooperative_multitasking>`_,如果换成拟人的修辞手法,他们大概是这样交流的:

    +
    Coroutine 1: Let me know when event A arrives. I'm done here before that.
    +Event manager: Okay. What about you, coroutine 2?
    +Coroutine 2: Um I've got nothing to do here before event B.
    +Event manager: Cool, I'll be watching.
    +Event manager: (after a while) Hey coroutine 1, event A is here!
    +Coroutine 1: Awesome! Let me see ... looks good, but I need event C now.
    +Event manager: Very well. Seems event B arrived just now, coroutine 2?
    +Coroutine 2: Oh wonderful! Let me store it in a file ... There! I'm all done.
    +Event manager: Sweet! Since there's no sign of event C yet, I'll sleep for a while.
    +(silence)
    +Event manager: Damn, event C timed out!
    +Coroutine 1: Arrrrh gotta kill myself with an exception :S
    +Event manager: Up to you :/
    +
    +
    +

    协程遇到IO事件将会等待并挂起自己当前的任务,但它不能被其他外部因素打断。所以当有很多协程的时候,并发就是由这些协程时不时地遇到事件时挂起来实现的。当然,如果你写了一个永远不会挂起的协程,它也不会允许任何并发的产生,因为其他的协程根本没有机会去运行。另外一方面,由于不会有多个协程同时运行,我们也大可不必我们所写的担心代码会搞乱一些共享的资源。现在你能理解上图中协程和线程红色色块堆叠方式不一样的原因了吧。

    +
    +

    小技巧

    +

    在Python中,async def 用来声明协程,await 用来将协程加入一个事件循环(即上文提到的将控制权交给事件管家)。

    +
    +
    +
    +

    异步I/O的利弊

    +

    异步I/O能够在同一条线程里面完成成千上万次的并发I/O操作。这样就能够节省多线程带来的CPU上下文切换时间以及内存。因此,在面临I/O密集型的任务时,异步I/O能够有效地使用CPU和内存资源,以达到相对较高的吞吐量。

    +

    并且,在Python中可以轻松自然地编写基于协程的代码。如果业务逻辑十分复杂,基于协程完成异步I/O的代码读起来也是行云流水。

    +

    但是,对于一个任务来说,异步I/O也会降低吞吐量。比如,调用函数 recv() ,它仅仅只是阻塞地等待返回值。而如果想要它变为异步,则必须要注册读事件,等待事件循环,尝试调用 ``recv()``函数,重复以上步骤,直到结果真正返回,然后再进行回调。所以用了协程之后,整个框架的开销实际上更大了。不过幸好有了像uvloop这样优秀的事件管理模块,上述的开销已经被极大地减少。即使是这样,和阻塞式的I/O相比,总提上开销还是大了些。

    +

    想要去为异步I/O的程序计算耗时也是比较费劲的,由于协作的特性,程序的可预测性会变差。举个栗子,你可能希望当前的协程暂停1秒钟。但是此时如果另外一个协程得到了控制权,开始运行,结果计算太久,花费了2秒钟,当我们再次回到刚才的协程时,2秒钟已经实实在在地过了,这么来看,sleep(1) 这个操作已经不能理解为暂停1秒,而应该理解为至少暂停1秒。所以,你应该努力做到让那些不使用 await 进行调用、同步执行的代码尽可能快得执行,让协程真正得“合作”起来。但即使你真的做到了让同步代码快速执行,有时程序的运行候难免还是会不如你所愿,所以要时刻记住这种在执行时间上的不确定性,这样才能方便更好地排查问题。

    +

    最后,异步编程其实是复杂的。要真正写好异步的代码比想象中的难很多,而且异步的代码比同步的代码更加难以调试。尤其是当整个团队都在处理同一段异步代码时,常常会遇到意想不到的问题。因此,这里给一个比较中肯的建议:仅仅在I/O密集型的高并发场景中谨慎使用异步I/O。不是说使用了异步I/O就能够给并发性能带来的巨大提升,它更像一把双刃剑。而且如果要处理一些对时间要求十分严格的任务,请考虑再三!

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/explanation/engine.html b/docs/zh/master/explanation/engine.html new file mode 100644 index 0000000..665df5e --- /dev/null +++ b/docs/zh/master/explanation/engine.html @@ -0,0 +1,688 @@ + + + + + + + + 引擎与连接 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    引擎与连接

    +

    GinoEngine is the core of GINO. It acts like a pool of +connections but also does the work of assembling everyone together:

    +../_images/engine.png +

    Under the hood, engine is associated with a specific dialect instance on +creation, e.g. asyncpg dialect. The dialect is actually a set of classes that +implements GINO dialect API, offering all the details about how to operate on +this specific database. In the diagram, gray color means internal, while green +means touchable by end users.

    +

    During creation, the engine will also ask the dialect to create a database +connection pool for it. The pool type is also a part of the dialect API, +because asynchronous database drivers usually have their own pool +implementation, thus their GINO dialects should hide such implementation +differences behind the unified diagram API for engine to use.

    +
    +

    注解

    +

    In SQLAlchemy, database drivers are supposed to follow the DB-API standard, +which does not usually provide a pool implementation. Therefore, SQLAlchemy +has its own pool implementation, created directly in engine. This is where +this diagram doesn't fit SQLAlchemy.

    +
    +

    The pool creates raw connections, not the GinoConnection +green in the diagram. The connection in the diagram is a many-to-one wrapper of +the raw connection, because of the reuse and lazy features, we'll get to that +part later. The connection is created by the engine, thus inherits the same +dialect, and is used for running queries.

    +

    On the outer side, SQLAlchemy queries can be executed directly on the engine or +connection. When on engine, it will try to acquire a reusable connection to +actually execute the connection, and release the connection after use.

    +
    +

    注解

    +

    Another difference to SQLAlchemy here: GINO execution methods always return +final results, while in SQLAlchemy accessing the result may cause further +implicit database accesses. Therefore GINO engine immediately releases the +connection when the execution method on the engine returns, but SQLAlchemy +can only release the connection implicitly when the result data is found +exhausted.

    +

    By immediately releasing a connection, GINO may not release the related raw +connection when the raw connection was reused from another parent +connection. We'll get to this later.

    +
    +

    GINO also supports implicit execution +without having to specify an engine or connection explicitly. This is done by +binding the engine to the db instance, also known as the +MetaData or the Gino instance. +You may possibly bind a GinoConnection instance, but that +is greatly not recommended because it is very much untested.

    +

    At last, as the ORM / CRUD feature, models are just add-ons on top of +everything else to generate queries. The parent model class is connected to a +db instance on creation, therefore the models can do implicit execution too +if their db has a bind.

    +

    Then let's get to some details.

    +
    +

    Creating Engines

    +

    GINO reuses the strategy system SQLAlchemy provides to create engines. The name +of GINO's strategy to create asynchronous GinoEngine is +just gino, but only available after gino is imported:

    +
    import gino, sqlalchemy
    +
    +async def main():
    +    e = await sqlalchemy.create_engine('postgresql://...', strategy='gino')
    +    # e is a GinoEngine
    +
    +
    +
    +

    小技巧

    +

    Please read this SQLAlchemy document +to learn about writing database URLs.

    +
    +

    Also the GINO strategy replaces the default driver of dialect postgresql:// +from psycopg2 to asyncpg, so that you don't have to replace the URL +as it may be shared between GINO and vanilla SQLAlchemy in parallel. +Alternatively, you can explicitly specify the driver to use by +postgresql+asyncpg://... or just asyncpg://....

    +

    GINO also offers a shortcut as gino.create_engine(), which only sets the +default strategy to gino and does nothing more. So here is an identical +example:

    +
    import gino
    +
    +async def main():
    +    e = await gino.create_engine('postgresql://...')
    +    # e is also a GinoEngine
    +
    +
    +

    As you may have noticed, when using the GINO strategy, +create_engine() returns a coroutine, which must be awaited +for result. Because it will create a database connection pool behind the scene, +and actually making a few initial connections by default.

    +

    For it is just SQLAlchemy create_engine(), the same rules of +parameters apply in GINO too. Well for now, GINO only supports a small amount +of all the parameters listed in SQLAlchemy document (we are working on it!):

    +

    For Dialect:

    + +

    For Engine:

    + +

    While these parameters are discarded by GINO:

    + +

    In addition, keyword arguments for creating the underlying pool is accepted +here. In the case of asyncpg, they are from create_pool(). +For example, we can create an engine without initial connections:

    +
    e = await gino.create_engine('postgresql://...', min_size=0)
    +
    +
    +

    Similar to SQLAlchemy, GINO also provides shortcut to create engine while +setting it as a bind. In SQLAlchemy it is like this:

    +
    import sqlalchemy
    +
    +metadata = sqlalchemy.MetaData()
    +metadata.bind = 'postgresql://...'
    +
    +# or in short
    +
    +metadata = sqlalchemy.MetaData('postgresql://...')
    +
    +
    +

    This implicitly calls create_engine() under the hood. However +in GINO, creating an engine requires await, it can no longer be hidden +behind a normal assignment statement. Therefore, GINO removed the assignment +magic in subclass Gino, reverted it to simple assignment:

    +
    import gino
    +
    +db = gino.Gino()
    +
    +async def main():
    +    # db.bind = 'postgresql://...' doesn't work!! It sets a string on bind
    +    engine = await gino.create_engine('postgresql://...')
    +    db.bind = engine
    +
    +
    +

    And provided a shortcut to do so:

    +
    engine = await db.set_bind('postgresql://...')
    +
    +
    +

    And another simpler shortcut for one-time usage:

    +
    db = await gino.Gino('postgresql://...')
    +
    +
    +

    To unset a bind and close the engine:

    +
    engine, db.bind = db.bind, None
    +await engine.close()
    +
    +
    +

    Or with a shortcut correspondingly:

    +
    await engine.pop_bind().close()
    +
    +
    +

    Furthermore, the two steps can be combined into one shortcut with asynchronous +context manager:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +
    +
    +

    Managing Connections

    +

    With a GinoEngine at hand, you can acquire connections +from the pool now:

    +
    conn = await engine.acquire()
    +
    +
    +

    Don't forget to release it after use:

    +
    await conn.release()
    +
    +
    +

    Yes this can be easily missing. The recommended way is to use the asynchronous +context manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    Here conn is a GinoConnection instance. As mentioned +previously, GinoConnection is mapped to an underlying raw +connection, as shown in following diagram:

    +../_images/connection.png +

    Each column has at most one actual raw connection, and the number is the +sequence the connections are created in this example. It is designed this way +so that GINO could offer two features for connection management: reuse and +lazy. They are keyword arguments on acquire() +and by default switched off.

    +
    +

    reuse

    +

    When acquiring a GinoConnection (2), GINO will borrow a +raw connection (1) from the underlying pool first, and assign it to this +GinoConnection (2). This is the default behavior of +acquire() with no arguments given. Even when +you are nesting two acquires, you still get two actual raw connection +borrowed:

    +
    async with engine.acquire() as conn1:
    +    async with engine.acquire() as conn2:
    +        # conn2 is a completely different connection than conn1
    +
    +
    +

    But sometimes conn2 may exist in a different method:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner()
    +
    +async def inner():
    +    async with engine.acquire() as conn2:
    +        # ...
    +
    +
    +

    And we probably wish inner could reuse the same raw connection in +outer to save some resource, or borrow a new one if inner is +individually called without outer:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner(conn2=None):
    +    if conn2 is None:
    +        async with engine.acquire() as conn2:
    +            # ...
    +    else:
    +        # the same ... again
    +
    +
    +

    This is exactly the scenario reuse could be useful. We can simply tell the +acquire() to reuse the most recent reusable +connection in current context by setting reuse=True, as presented in this +identical example:

    +
    async def outer():
    +    async with engine.acquire() as conn1:
    +        await inner(conn1)
    +
    +async def inner():
    +    async with engine.acquire(reuse=True) as conn2:
    +        # ...
    +
    +
    +

    Back to previous diagram, the blue GinoConnection +instances (3, 4, 6) are "reusing connections" acquired with reuse=True, +while the green ones (2, 5, 7) are not, thus they become "reusable +connections". The green reusable connections are put in a stack in current +context, so that acquire(reuse=True) always reuses the most recent +connection at the top of the stack. For example, (3) and (4) reuse the only +available (2) at that moment, therefore (2, 3, 4) all map to the same raw +connection (1). Then after (5), (6) no longer reuses (2) because (5) is now the +new head of the stack.

    +
    +

    小技巧

    +

    By context, we are actually referring to the context concept in +contextvars the +new module in Python 3.7, and its partial backport aiocontextvars. Simply speaking, you may +treat a series of function calls in a chain as in the same context, even if +there is an await. It's something like a thread local in asyncio.

    +
    +

    GinoConnection (2) may be created through +acquire(reuse=True) too - because the stack is empty before (2), there is +nothing to reuse, so (2) upgraded itself to a reusable connection.

    +

    Releasing a reusing connection won't cause the reused raw connection being +returned to the pool, only directly releasing the reused +GinoConnection can do so. Connections should be released +in the reversed order as they are acquired, but if the reused connection is +released before reusing connections by accident, then all the reusing +connections depending on it will turn closed because they are reusing the same +raw connection which is returned to the pool, any further execution will fail. +For example, if (3) is released first, then (2) and (4) are still functional. +But if (2) is released first, then (3) and (4) will be released implicitly and +are no longer usable any more.

    +
    +
    +

    lazy

    +

    As you may have found, GinoConnection (5) does not have +an underlying raw connection, even when it is reused by (6). This is because +both (5) and (6) set lazy=True on acquire.

    +

    A lazy connection will not borrow a raw connection on creation, it will only do +so when have to, e.g. when executing a query or starting a transaction. For +example, GinoConnection (7) is acquired lazily without a +raw connection, and (8) is only created when a query is executed on (7):

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +
    +
    +

    On implementation level, lazy is extremely easy in +acquire(): if lazy=False then borrow a raw +connection, else do nothing. That's it. Before executing a query or starting a +transaction, GinoConnection will always try to borrow a +raw connection if there is none present. This allows GINO to "transiently +release" a raw connection, while all GinoConnection +mapped to this raw connection are put in lazy mode (again). This is especially +useful before you need to run some networking tasks in a database-related +context - the networking task may take a long time to finish, we don't want to +waste a connection resource checked out for nothing. For example:

    +
    async with engine.acquire(lazy=True) as conn:  # (7)
    +    await conn.scalar('select now()')          # (8)
    +    await conn.release(permanent=False)        # release (8)
    +    await asyncio.sleep(10)                    # simulate long I/O work
    +    await conn.scalar('select now()')          # re-acquire a new raw connection,
    +                                               #   not necessarily the same (8)
    +
    +
    +

    When used together with reuse, at most one raw connection may be borrowed +for one reusing chain. For example, executing queries on both (5) and (6) will +result only one raw connection checked out, no matter which executes first. It +is also worth noting that, if we set lazy=False on (6), then the raw +connection will be immediately borrowed on acquire, and shared between both (5) +and (6). It's been quite a while, let me post the same diagram again:

    +../_images/connection.png +
    +
    +

    reusable

    +

    Usually, you don't have to worry about the two options reuse and lazy, +using the default acquire() will always create +a concrete GinoConnection with a new raw connection with +it. It is only that they are by default reusable (the green ones). If you need +an absolutely isolated unique connection that has no risk being reused, you may +use reusable=False on acquire. As shown in the diagram, the unreusable +GinoConnection is an orphan away from any stack:

    +
    async with engine.acquire():                    # (2)
    +    async with engine.acquire(reusable=False):  # the unreusable connection
    +        async with engine.acquire(reuse=True):  # (3)
    +
    +
    +

    Unreusable connections can be lazy. But it is usually meaningless to specify +both reuse=True and reusable=False at the same time, because reusing +connections are always unusable - they are also not in the stack. You cannot +reuse a reusing connection, you only reuse a reusable connection in the stack. +Making a reusing connection unreusable doesn't make its related reusable +connection unreusable. Hmm if this is getting more confusing, just don't use +acquire(reuse=True, reusable=False) unless you know what it does.

    +
    +
    +

    current_connection

    +

    Except for all scenarios supported by above three options, there is still one +left out: we may want to acquire a reusing-only connection. There is no such +option to do so, but GINO could do the same thing through +current_connection which is always the reusable +GinoConnection at the top of current stack, or None +if current stack is empty.

    +
    +

    小技巧

    +

    The different between current_connection +and acquire(reuse=True) is, the +latter always produces a GinoConnection, while the +former may not.

    +
    +
    +
    +
    +

    Executing Queries

    +

    Once you have a GinoConnection instance, you can start +executing queries with it. There are 6 variants of the execute method: +all(), +first(), +one(), +one_or_none(), +scalar() and +status(). They are basically the same: +accepting the same parameters, calling the same underlying methods. The +difference is how they treat the results:

    +
      +
    • all() returns all results in a +list, which may be empty when the query has no result, empty but +still a list.

    • +
    • first() returns the first result directly, +or None if there is no result at all. There is usually some optimization +behind the scene to efficiently get only the first result, instead of loading +the full result set into memory.

    • +
    • one() returns exactly one result. If there +is no result at all or if there are multiple results, an exception is raised.

    • +
    • one_or_none() is similar to +one(), but it returns None if there is +no result instead or raising an exception.

    • +
    • scalar() is similar to +first(), it returns the first value of the +first result. Quite convenient to just retrieve a scalar value from database, +like NOW(), MAX(), COUNT() or whatever generates a single value. +None is also returned when there is no result, it is up to you how to +distinguish no result and the first value is NULL.

    • +
    • status() executes the query and discard all +the query results at all. Instead it returns the execution status line as it +is, usually a textual string. Note, there may be no optimization to only +return the status without loading the results, so make your query generate +nothing if you don't want any result.

    • +
    +

    By "result", I meant RowProxy of SQLAlchemy - an +immutable row instance with both tuple and dict interfaces. +Database values are translated twice before they are eventually stored in a +RowProxy: first by the database driver (dialect) +from network payload to Python objects (see Type Conversion of +how asyncpg does this), second by SQLAlchemy +result_processor() depending on the actual +type and dialect.

    +

    The arguments taken by these 4 methods are identical to the ones accepted by +SQLAlchemy execute() (click to read more), +usually a plain string of SQL directly or a SQLAlchemy query clause, followed +by query parameters. In the case when multiple dictionaries are given to +multiparams, all 4 methods will always return None discarding all +results. Likewise, the parameter values are processed twice too: first by +bind_processor() then the database driver.

    +

    GINO also supports SQLAlchemy +execution_options() provided either on +engine level, +connection level or on +queries. At +the moment we are working on being compatible with SQLAlchemy execution +options. In the mean while, GINO provides several new execution options, for +example enabling return_model and providing a model will make +all() and +first() return ORM model instance(s) instead +of RowProxy instance(s). See also +execution_options() for more information.

    +

    In addition, GINO has an iterate() method to +traverse the query results progressively, instead of loading all the results at +once. This method takes the same arguments as the other 4 execute methods do, +and follows the same rule of data handling. For now with asyncpg, this creates +a server-side cursor.

    +
    +
    +

    Implicit Execution

    +

    Acquire a GinoConnection and execute queries on it, that +is the most explicit way. You can also execute queries on a +GinoEngine instance. In this case, a connection will be +acquired with reuse=True for you implicitly, and released after returning:

    +
    await engine.scalar('select now()')
    +
    +
    +

    Equals to:

    +
    async with engine.acquire(reuse=True) as conn:
    +    await conn.scalar('select now()')
    +
    +
    +

    This allows you to easily write connectionless code. For example:

    +
    async def get_now():
    +    return await engine.scalar('select now()')
    +
    +async def main():
    +    async with engine.acquire():
    +        now = await get_now()
    +        await engine.status('UPDATE ...')
    +
    +
    +

    In this example, main() will take only one raw connection. get_now() +can also work alone out of any acquire() context, thanks to reuse.

    +

    Furthermore, GINO provides the same query APIs on Gino +directly. They are simply delegates to corresponding API methods on the +bind. This allows even engine-less programming:

    +
    db = gino.Gino()
    +
    +async def get_now():
    +    return await db.scalar('select now()')
    +
    +async def main():
    +    async with db.with_bind('postgresql://...'):
    +        now = await get_now()
    +        await db.status('UPDATE ...')
    +
    +
    +
    +

    注解

    +

    In this example we didn't put the two queries in an acquire() block, so +they might be executed in two different connections.

    +
    +

    At last, the SQLAlchemy implicit execution +on queries also work in GINO, under an extension named gino:

    +
    await users_table.select().gino.all()
    +
    +
    +

    By default, the extension GinoExecutor is injected on +Executable as a property of name gino +at the creation of Gino instance. Therefore, any +Executable object has the gino +property for implicit execution. Similarly, the execution methods calls the +corresponding ones on the bind of the db instance.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/explanation/sa20.html b/docs/zh/master/explanation/sa20.html new file mode 100644 index 0000000..a526660 --- /dev/null +++ b/docs/zh/master/explanation/sa20.html @@ -0,0 +1,830 @@ + + + + + + + + SQLAlchemy 2.0 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    SQLAlchemy 2.0

    +

    This article explains the new updates from SQLAlchemy 1.4 and 2.0, as well as how GINO +adapts to such changes.

    +

    SQLAlchemy 2.0 will +deliver many breaking API changes, and SQLAlchemy 1.4 will be the "interim" +version for people to eventually upgrade their software to use SQLAlchemy 2.0.

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    GINO

    SQLAlchemy

    Dialect

    Comments

    1.0.x

    1.3.x

    Custom

    Current (old-)stable.

    1.1.x

    1.3.x

    Custom

    Next old-stable.

    1.2.x

    1.3.x

    Custom

    Future old-stable (maybe).

    1.4.x

    1.4.x

    Upstream

    2.0 Interim.

    2.0.x

    2.0.x

    Upstream

    Future stable.

    2.1.x

    2.0.x

    Upstream

    Future stable iterations.

    +

    To make things easier, GINO will (luckily) also follow the same versions for the transition. That is, +GINO 1.4 will be requiring SQLAlchemy 1.4, providing similar pre-1.4-compatible APIs +with deprecations and options to switch to 2.0-API; GINO 2.0 needs SQLAlchemy 2.0 and +provides new APIs only.

    +

    At the same time, GINO 1.1, 1.2 and 1.3 will be reserved as the old-stable versions with +new features added on SQLAlchemy 1.3. And GINO post-2.0 won't match SQLAlchemy versions.

    +
    +

    The Async Solution

    +

    Among all the exciting updates in SQLAlchemy 1.4 / 2.0, native async support is the most +significant change for GINO. Simply speaking, SQLAlchemy 1.4 decided to make use of +greenlet to mix asynchronous stuff into current code base, avoiding making everything +async.

    +

    Let's say we have an asynchronous method to create an asyncpg connection:

    +
    import asyncpg
    +
    +async def connect():
    +    return await asyncpg.connect("postgresql:///")
    +
    +
    +

    And an end-user method to use it:

    +
    async def main():
    +    conn = await connect()
    +    now = await conn.fetchval("SELECT now()")
    +
    +
    +

    Now instead of directly calling connect() from main(), I would like to add some +additional logic - let's say, a sanity check:

    +
    async def safe_connect():
    +    conn = await connect()
    +    try:
    +        await conn.execute("SELECT 1")
    +    except Exception:
    +        return None
    +    else:
    +        return conn
    +
    +
    +

    Then the end-user should modify main() to:

    +
     async def main():
    +     conn = await safe_connect()
    +     if conn:
    +         now = await conn.fetchval("SELECT now()")
    +
    +
    +

    OK, everything works so far, as they are all regular async code. Here's the interesting +part: safe_connect() must not be an async def method. With SQLAlchemy 1.4+, we +could:

    +
     from sqlalchemy.util import await_only, greenlet_spawn
    +
    + def sync_safe_connect():
    +     conn = await_only(connect())
    +     try:
    +         await_only(conn.execute("SELECT 1"))
    +     except Exception:
    +         return None
    +     else:
    +         return conn
    +
    + async def safe_connect():
    +     return await greenlet_spawn(sync_safe_connect)
    +
    +
    +

    Behind the scene, greenlet_spawn() runs the given "sync" method in a greenlet, which +uses await_only() to switch to the event loop and bridge the underlying async +methods. As sync_safe_connect() is just a normal Python method, you can imagine how +it works together with lots of other "sync" code asynchronously.

    +

    We're not going deeper into the implementation, but this is basically how SQLAlchemy 1.4 +mixes asyncpg driver into its "sync" code base, and still being able to provide async +APIs on top of them.

    +
    +
    +

    Async SQLAlchemy

    +

    Although greenlet might be the only way to practically port SQLAlchemy to the async +world natively without having to maintain 2 copies of the same code, introducing +implicit asynchronous to a large sync code base is still a risky move.

    +

    The sync library existed for years, with many assumptions like using threading.Lock +to control concurrency. When switching to asynchronous programming, such primitives and +assumptions usually need to be reviewed and probably replaced by e.g. asyncio.Lock. +However, the implicit approach is so convenient that most of the blocking code just +works without any changes at all, lowering the odds to review and find possible issues.

    +

    As a matter of fact, some issues can only be revealed under (heavy) concurrency. For +example, threading.Lock.acquire() actually works fine in a single coroutine, but 2 +concurrent coroutines +acquiring the same threading.Lock may easily block the main thread forever. The same +applies to data structures like queues, etc. In short, there must be nothing to block +the main thread.

    +

    Lastly, having implicit asynchronous is a potential maintenance risk. Because the +context switches are usually hidden behind regular sync methods, it is easy to forget +such methods may lead to concurrency issues. Treating coroutines as OS threads is a good +idea and it usually works, but they are fundamentally different. Extra care is always +needed when trying to support both sync and async with the same code base.

    +

    However, such risks are mostly contained within SQLAlchemy itself. End-users are still +using regular explicit asynchronous APIs to leverage the async DB drivers. With proper +reviewing, testing and sufficient community exposure (that's GINO's part), it is still +possible and reliable to have a single SQLAlchemy with 2 sets of APIs (sync + async). +For sure, the APIs are not 100% identical - for example, the ORM lazy-loading won't work +because there is no place for await in accessing attributes (GINO doesn't like such +implicitness anyways, so yeah).

    +

    To quickly get a picture of async SQLAlchemy (Core), here's a sample from SQLAlchemy:

    +
    import asyncio
    +
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def async_main():
    +    engine = create_async_engine(
    +        "postgresql+asyncpg://scott:tiger@localhost/test", echo=True,
    +    )
    +
    +    async with engine.begin() as conn:
    +        await conn.run_sync(meta.drop_all)
    +        await conn.run_sync(meta.create_all)
    +
    +        await conn.execute(
    +            t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
    +        )
    +
    +    async with engine.connect() as conn:
    +
    +        # select a Result, which will be delivered with buffered
    +        # results
    +        result = await conn.execute(select(t1).where(t1.c.name == "some name 1"))
    +
    +        print(result.fetchall())
    +
    +
    +asyncio.run(async_main())
    +
    +
    +
    +
    +

    Auto-Commit Complication

    +

    After using PostgreSQL for a long time, I took many features for granted. The +auto-commit feature is one of them. We all know that BEGIN starts a transaction and +COMMIT / ROLLBACK ends it. But what is happening to SQL statements that is not +wrapped in BEGIN ... COMMIT blocks?

    +
    +

    If you do not issue a BEGIN command, then each individual statement has an +implicit BEGIN and (if successful) COMMIT wrapped around it.

    +

    —PostgreSQL Documentation, 3.4. Transactions

    +
    +

    And yes, implicit ROLLBACK if not successful. This is not directly named as an +"auto-commit" feature, but PostgreSQL does enforce it. Now imagine a database whose +"auto-commit" feature can be turned off - such individual statements are either executed +without ACID-transactions at all (no surprise, there are databases without ACID :doge:), +or the session is left with a open-ended transaction to be further committed.

    +

    PEP 249 (DB-API 2.0) - the standard for +Python database drivers - was created in such background stories. The assumption it made +appears to me like, the database may probably not support ACID; if it does, auto-commit +is usually not supported. Because it defined only commit() and rollback() on a +connection, but no begin(). So I think DB-API assumes that, when executing +statements, the connection is automatically put in a transaction (if ACID is supported), +and you have to call commit() to persist your changes. Closing a connection will +cause pending transactions rolled back automatically.

    +
    +

    Note that if the database supports an auto-commit feature, this (the auto-commit +feature -- GINO comments) must be initially off.

    +

    —PEP 249

    +
    +

    As the API behavior is defined, database drivers for even e.g. PostgreSQL with enforced +"auto-commit" has to mimic such behavior. For example, psycopg2 will automatically emit +a BEGIN to the database upon the first execution by default, so that such execution +is not auto-committed. The implicit transaction boundary is a very evil thing - people +constantly leaves transactions open (ever seen IDLE IN TRANSACTION?), sometimes even +holding database locks and eventually causing a deadlock storm.

    +

    To work around this workaround, PEP 249 does say:

    +
    +

    An interface method may be provided to turn it (the auto-commit feature) back on.

    +
    +

    So for psycopg2, one could do this:

    +
    import psycopg2
    +
    +conn = psycopg2.connect("postgresql:///")
    +conn.autocommit = True
    +conn.cursor().execute("SELECT now()")
    +
    +
    +

    Now the database correctly receives this SELECT statement only, without any implicit +BEGIN surprises. But when we want to have explicit transactions, DB-API only gives +us 2 options: 1) Do it manually:

    +
    conn.cursor().execute("BEGIN")
    +conn.cursor().execute("UPDATE ...")
    +conn.cursor().execute("COMMIT")
    +
    +
    +

    Or 2) turn auto-commit off again:

    +
    conn.autocommit = False
    +conn.cursor().execute("UPDATE ...")
    +conn.commit()
    +
    +
    +

    I know this is frustrating (or maybe people have accepted it), but newer database +drivers like asyncpg does provide a cleaner API, by not complying to PEP 249:

    +
    import asyncpg
    +
    +async def main():
    +    conn = await asyncpg.connect("postgresql://")
    +
    +    print(await conn.fetchval("SELECT now()"))  # SELECT now();
    +
    +    async with conn.transaction():              # BEGIN;
    +        await conn.execute("UPDATE ...")        # UPDATE ...;
    +                                                # COMMIT;
    +
    +
    +

    It's much cleaner to see what's actually happening on the wire to the database. This is +also how GINO works.

    +
    +
    +

    SQLAlchemy for DB-API

    +

    Because SQLAlchemy is built on PEP 249 (DB-API 2.0), its API is also greatly affected by +the PEP standard. For example, imagine what SQL is actually executed by this code:

    +
    import sqlalchemy as sa
    +
    +e = sa.create_engine("postgresql:///", future=True)
    +with e.connect() as conn:
    +    conn.scalar(sa.text("SELECT now()"))
    +
    +
    +

    Only SELECT now()? No. Here's the answer:

    +
    with e.connect() as conn:                 # BEGIN; SELECT version(); ...; ROLLBACK;
    +    conn.scalar(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                          # ROLLBACK;
    +
    +
    +
    +

    注解

    +

    We are using SQLAlchemy 2.0 API for simplification, by setting future=True using +SQLAlchemy 1.4. It's way more complicated in SQLAlchemy 1.3 and I don't want to get +into that.

    +
    +

    The reason behind this is, connections have "auto-commit" turned off by default. If +there is no transaction, an implicit BEGIN will be automatically executed upon the +first statement. This applies to async SQLAlchemy too - even if the underlying asyncpg +is not DB-API-compliant, the AsyncpgDialect in SQLAlchemy still wrapped asyncpg and +simulated a compatible DB-API. Like this:

    +
    import sqlalchemy as sa
    +from sqlalchemy.ext.asyncio import create_async_engine
    +
    +async def main():
    +    e = create_async_engine("postgresql+asyncpg:///")
    +    async with e.connect() as conn:                  # BEGIN; SELECT version(); ...; ROLLBACK;
    +        await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                     # ROLLBACK;
    +
    +
    +

    If you want to modify the database permanently, you have to commit() the implicit +transaction explicitly:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("UPDATE ..."))    # BEGIN; UPDATE ...;
    +         await conn.commit()                          # COMMIT;
    +
    +
    +

    Or use the explicit transaction API:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Please note that, SQLAlchemy 2.0 doesn't allow soft-nested transactions. In other words, +you cannot nest 2 async with conn.begin(): blocks like this:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         async with conn.begin():      # BEGIN;
    +             async with conn.begin():  # Error: a transaction is already begun
    +                 ...
    +
    +
    +

    This limitation applies to implicit transactions too, even though it's weird:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    You have to explicitly close this implicit transaction in order for an explicit +transaction to start successfully:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +         await conn.rollback()                        # ROLLBACK;
    +         async with conn.begin():                     # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    Similar to Core, SQLAlchemy ORM follows the same principal. Grab a session, use +it without begin(), and when you want to commit, commit(). Or, use an explicit +transaction in a with session.begin(): block. Personally I don't like that much, but +it is how SQLAlchemy manages transactions. Please follow the link above to read more.

    +
    +
    +

    SQLAlchemy AUTOCOMMIT

    +

    I know you already miss the WYSIWYG asyncpg and GINO API. Hang in there, let's build +GINO 1.4 together with the SQLAlchemy AUTOCOMMIT feature.

    +

    To turn AUTOCOMMIT back on, we need to set the isolation_level to AUTOCOMMIT in +execution_options:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Hooray! No more implicit BEGIN magic. We're one step closer.

    +
    +

    注解

    +

    There is also a keyword argument:

    +
     e = create_async_engine(
    +     "postgresql+asyncpg:///",
    +     isolation_level="AUTOCOMMIT",
    + )
    +
    +
    +

    But this is implemented very differently than execution_options, and I don't +think it's working for GINO's use case.

    +
    +

    The next question is, how do we explicitly start a transaction? Let's try begin():

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         async with conn.begin():  # Error: a transaction is already begun
    +             ...
    +
    +
    +

    Wait a second ... we've seen this error before! It is the implicit transaction +conflicting with the explicit transaction. But I thought we're in AUTOCOMMIT mode? Even +though the isolation_level tell the driver not to send BEGIN to the database, +SQLAlchemy still manages library-level transaction objects. We have to close the virtual +implicit transaction before starting an explicit transaction:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.begin():                     # no-op
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +
    +
    +

    Well, not quite what we expected. With AUTOCOMMIT set, all of begin(), commit() +and rollback() become no-ops.

    +

    Similar to the answers in psycopg2, we have 2 options here too: 1) manually execute +transaction-control SQLs, or 2) turn off AUTOCOMMIT temporarily. As we want to be more +compatible with SQLAlchemy, let's try 2):

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +         await conn.rollback()                        # no-op
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +
    +

    It's working! According to SQLAlchemy docs, execution_options() creates a shallow +copy of the connection, and apply new values only to the copy. So the original +connection should still be in AUTOCOMMIT, right? Well...

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         await conn.execute(sa.text("SELECT now()"))  # BEGIN; SELECT now();
    +                                                      # ROLLBACK;
    +
    +
    +

    Unfortunately, the implicit transaction is haunting us again. This is because both the +original connection and its shallow copy points to the same "DB-API" connection (in this +case, a SQLAlchemy wrapper of asyncpg connection), and setting isolation_level +modifies the value on "DB-API" connection.

    +

    Returning a SQLAlchemy connection back to the pool resets the isolation_level to its +default value, and acquiring the same connection again will initialize the +isolation_level with values from execution_options of the engine. But if we want +to keep using the same connection without returning, we have to manually overwrite its +isolation_level again:

    +
     async def main():
    +     e = ...
    +     async with e.connect() as conn:
    +         ...
    +         async with conn.execution_options(
    +             isolation_level="READ COMMITTED"
    +         ).begin():                                   # BEGIN;
    +             await conn.execute(sa.text("UPDATE.."))  # UPDATE ...;
    +                                                      # COMMIT;
    +
    +         conn.execution_options(isolation_level="AUTOCOMMIT")
    +         await conn.execute(sa.text("SELECT now()"))  # SELECT now();
    +
    +
    +

    Eventually we made it! 🎉

    +

    Encapsulating all such logic, GINO 1.4 could then provide decent WYSIWYG APIs again:

    +
    import gino
    +
    +async def main():
    +    engine = await gino.create_engine("postgresql:///")
    +    async with engine.acquire() as conn:
    +        await conn.scalar("SELECT now()")    # SELECT now();
    +
    +        async with conn.transaction():       # BEGIN;
    +            await conn.status("UPDATE ...")  # UPDATE ...;
    +                                             # COMMIT;
    +
    +
    +
    +

    提示

    +

    Now I feel that "implementing" auto-commit feature is more like restoring to the +original database behavior, and having auto-commit turned off by default should be +considered as a new feature called "auto-begin" or "implicit transaction". And it's +a bad design introduced in early PEP 249, affecting SQLAlchemy and the ecosystem.

    +
    +
    +
    +

    Isolation Levels

    +

    By far, we only used 2 isolation_level values:

    +
      +
    • AUTOCOMMIT

    • +
    • READ COMMITTED

    • +
    +

    AUTOCOMMIT is not a valid PostgreSQL isolation level. It's only recognized and +consumed by the SQLAlchemy asyncpg dialect to bypass the "auto-begin" simulation.

    +

    READ COMMITTED is the default PostgreSQL isolation level. You can verify this by +executing a SQL directly in a transaction:

    +
    # BEGIN;
    +BEGIN
    +
    +# SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +# ROLLBACK;
    +ROLLBACK
    +
    +
    +

    To start a transaction in a different isolation level, you may:

    +
     # BEGIN ISOLATION LEVEL SERIALIZABLE;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    As mentioned earlier, all PostgreSQL statements are executed in a transaction. If no +explicit BEGIN in place, an implicit transaction is used. So this SQL also works +individually:

    +
    # SHOW TRANSACTION ISOLATION LEVEL;
    + transaction_isolation
    +-----------------------
    + read committed
    +(1 row)
    +
    +
    +

    But how could we modify the isolation level of such implicit transactions? The answer is +to set isolation level session-wise. This affects all subsequent transactions, including +both implicit and explicit ones, except for explicit transactions with explicit +isolation levels:

    +
     # SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    + SET
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # BEGIN;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  serializable
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    + # BEGIN ISOLATION LEVEL READ COMMITTED;
    + BEGIN
    +
    + # SHOW TRANSACTION ISOLATION LEVEL;
    +  transaction_isolation
    + -----------------------
    +  read committed
    + (1 row)
    +
    + # ROLLBACK;
    + ROLLBACK
    +
    +
    +

    Then let's see how SQLAlchemy with asyncpg solves this problem:

    +
     async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "SERIALIZABLE"},
    +     )
    +     async with e.connect() as conn:
    +         async with conn.begin():  # BEGIN ISOLATION LEVEL SERIALIZABLE;
    +             await conn.execute(sa.text("UPDATE ..."))  # UPDATE ...;
    +                                                        # COMMIT;
    +
    +
    +

    Under the neath, SQLAlchemy is leveraging asyncpg's +Connection.transaction(isolation="...") to set isolation level per transaction. In +GINO, we just need to store the user-defined isolation level, and set before +transactions.

    +

    But there are 2 issues:

    +
      +
    • User-defined isolation level is not applied in PostgreSQL implicit transactions +(a.k.a. auto-commit statements), because no one SET SESSION.

    • +
    • asyncpg has a bug that Connection.transaction(isolation="read_committed") always +emit BEGIN without explicit isolation level, regardless of the actual default +isolation level.

    • +
    +

    The asyncpg bug should be fixed from upstream, but we could leverage a session-wide +isolation level setter from base SQLAlchemy dialect for PostgreSQL:

    +
     import sqlalchemy as sa
    + from sqlalchemy import event
    + from sqlalchemy.dialects.postgresql.base import PGDialect
    + from sqlalchemy.ext.asyncio import create_async_engine
    +
    +
    + async def main():
    +     e = create_async_engine(
    +         "postgresql+asyncpg:///",
    +         execution_options={"isolation_level": "AUTOCOMMIT"},
    +     )
    +
    +     def set_isolation_level(dbapi_conn, record):
    +         PGDialect.set_isolation_level(
    +             e.sync_engine.dialect,
    +             dbapi_conn,
    +             "SERIALIZABLE",
    +         )
    +
    +     event.listen(e.sync_engine, "connect", set_isolation_level)
    +
    +     async with e.connect() as conn:
    +         print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL")))
    +         # Outputs: serializable
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/explanation/why.html b/docs/zh/master/explanation/why.html new file mode 100644 index 0000000..75d6036 --- /dev/null +++ b/docs/zh/master/explanation/why.html @@ -0,0 +1,416 @@ + + + + + + + + 为什么要用异步 ORM? - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    为什么要用异步 ORM?

    +

    Conclusion first: in many cases, you don't need to use an asynchronous ORM, or even +asyncio itself. But when you do, it's very important to get it done correctly.

    +
    +

    When asyncio Helps

    +

    As Mike Bayer - the author of SQLAlchemy - pointed out, even +Python itself can be slower than the database operation in a stereotypical +business-style CRUD-ish application, because the modern databases are so fast when the +query is simple, and this kind of application usually deploys the database in a super +reliable network. In this case, it doesn't make sense to say "Hey I wanna use asyncio +because I love this asynchronous ORM".

    +

    The problem asyncio solves is quite different: when you need to deal with a lot of +concurrent tasks, some of which may block on I/O for arbitrary time, asyncio could +largely leverage the scalability with cooperative multitasking. This is explained in +异步编程基础. So the ultimate reason to use asyncio should be the application itself, +not the database.

    +

    For example, a chat server may need to talk to tens of thousands of clients at the same +time. The clients are idle for most of the time, because the user didn't send a message +in seconds, which is thousands of times longer than the time needed by the server to +handle the message. Obviously the last thing you want is to have tens of thousands of OS +threads to wait for each of those clients to reply, as it'll disproportionately consume +way too much hardware resource. In contrast, asyncio could easily handle this +concurrency with reasonably tiny overhead.

    +../_images/263px-Minimum-Tonne.svg.png +

    Another example is authentication using OpenID Connect Authorization Code Flow as a Client. If +the Token Endpoint of the Authorization Server responds fast, everything is fine. But +once it's delayed for even just a few seconds due to unreliable Internet or overloaded +server or whatever reasons, the Client would face a concurrency challenge. According to +Liebig's law, the +minimum throughput of the Client or the Server determines the overall performance. +Comparing to asyncio, it's not wise to use threading model to build the Client and rely +on the Internet which may make the Client the shortest stave.

    +

    Arbitrary delays may happen in the database too. In PostgreSQL, you can LISTEN to +some asynchronous events that happens unpredictably. Some normal bulk queries may also +take a long time to run, and the database locks can occasionally block too, especially +the dead ones. But these are usually not considered as a high-concurrency scenario, +because the database will possibly hit its bottleneck before your server does, or there +are smarter ways to handle such situations than asyncio. Nevertheless, the database +could be the reason to use asyncio, depending on the actual case.

    +

    In closing, there are scenarios when asyncio could help with concurrency issues. Now +that we know we will write an asynchronous server, then the next question is:

    +
    +
    +

    How to Access Database

    +

    The first thing to balance here is ORM - how much convenience would you like to +sacrifice for execution performance. As we are talking about ORM, let's assume we need +at least some level of abstraction over raw SQL, or else low-level tools like asyncpg +would be a neat choice. Don't get me wrong - asyncpg is great and convenient to use, but +there won't be such a question "why asynchronous ORM" if we're not seeking an objective +layer over bare SQL. It's totally reasonable and sometimes beneficial and fun to play +with SQL in asynchronous contexts, it's just out of our scope here.

    +

    Because simple queries are executed so fast in the database, one obvious but wrong +approach is to just run the query in the main thread. It won't work because it will 100% +cause a dead lock - not a typical database dead lock, but a hybrid one between the +connection pool and cooperative multitasking. For example, here's a very simple server:

    +
    import asyncio
    +from fastapi import FastAPI
    +from sqlalchemy import create_engine
    +from sqlalchemy.orm import sessionmaker
    +
    +app = FastAPI()
    +e = create_engine("postgresql://localhost")
    +Session = sessionmaker(bind=e)
    +
    +
    +@app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    This follows advices found in When do I construct a Session, when do I commit it, and when do I close it? from SQLAlchemy - linking +the session scope to the request scope. It works fine within 10 concurrent requests, +but freezes miserably for any number beyond 10: all requests hang and the only way to +stop the server is kill -9.

    +

    What just happened is called a resource starvation. While the first 10 +requests were waiting for the async work (sleep(0.1)), the 11th request came in and +tried to acquire a new database connection from the pool. But the connection pool is +already exhausted (default max_overflow=10) by the first 10 requests, the 11th +request was then blocked by the acquisition. However, this acquisition is unfortunately +a blocking operation, therefore the main thread is blocked and the first 10 requests +lost their chance to finish the async work and return their connection back to the pool +to resume the 11th's acquisition.

    +

    The root cause of such starvation is making asynchronous calls in a transaction +scope created implicitly by the default autocommit=False. So other than limiting +the number of concurrent requests down to 10, a more reasonable solution is to avoid +doing asynchronous calls in transaction scopes by explicitly ending them:

    +
    @app.get("/")
    +async def read_root():
    +    session = Session()
    +    try:
    +        now = session.execute("SELECT now()").scalar()
    +        session.rollback()  # return the connection to avoid starvation
    +        await asyncio.sleep(0.1)  # do some async work
    +        return str(now)
    +    finally:
    +        session.close()
    +
    +
    +

    Because of the implicit nature of typical ORMs, it's very hard to identify all such +transaction scopes and make sure there's no await in them - you may have started +an implicit transaction by simply accessing a property like current_user.name. In +practice, it is an impossible mission to correctly mix blocking ORM code with +asynchronous programming in the same thread. So never do this.

    +

    What about thread pool?

    +

    The idea is to defer all blocking code into a thread pool, and run asynchronous +cooperative multitasking in the main thread. This is theoretically possible, but the +same implicit ORM issue keeps haunting us - how do you guarantee there is no blocking +calls in the main thread? Taking one step back, let's assume we could do this cleanly. +What would the code look like? There must be a clear API as the logical separation +between the blocking and asynchronous code - the server executes major DB-free business +logic in the main thread with asynchronous programming, and calls blocking methods for +encapsulated database operations in a thread pool.

    +

    This reminds me of its opposite pattern, where the main server runs in blocking mode, +deferring I/O intensive operations into a task queue like Celery. Both approaches solve +the problem, the reason to choose one over the other is on the concentration of business +logic - you'd want to write the majority of your code in the main server, and defer only +sub-tasks or low priority operations into a thread pool or task queue. In reality, using +a task queue to e.g. send emails is a more reasonable approach. If you're doing that in +the opposite way, you may want to reconsider the design.

    +

    In summary, other than using raw SQL, the existing typical ORMs could not serve +asynchronous programming well enough. We need true asynchronous ORMs.

    +
    +
    +

    Asynchronous ORM Done Right

    +

    I would describe a proper asynchronous ORM as follows:

    +
    +

    Don't Starve.

    +

    The very basic requirement for an asynchronous ORM is to avoid resource starvation, at +least avoid the part caused by the ORM design. The fix is actually pretty +straightforward - just make everything async.

    +

    In the example above, if the connection acquisition is asynchronous, it won't block the +main thread any more (it'll block it's own coroutine instead). Therefore, the other +tasks would get a chance to finish the async work and return the connection back to the +pool, thus the starvation is avoided:

    +
    @app.get("/")
    +async def read_root():
    +    async with db.acquire() as conn:  # }
    +        async with conn.begin():      # } These two won't block the main thread
    +            now = await db.scalar("SELECT now()")
    +            await asyncio.sleep(0.1)  # do some async work
    +            return str(now)
    +
    +
    +

    Even though this code won't cause any resource starvation, using await within +database transactions is still strongly discouraged, unless it is for the database query +or absolutely necessary. Because after yielding the execution, we have no idea when we +can resume the following execution. That leads to a hanging transaction or at least an +unused database connection for a moment. Doing so won't kill the server immediately, but +it has a few disadvantages:

    +
      +
    1. As the database connection pool is usually much smaller than the asynchronous server +concurrency, this kind of code caps the concurrency down to the DB pool level.

    2. +
    3. Taking a database connection from the pool for nothing is a waste of resource, +especially when the database pool is the shortest stave.

    4. +
    5. Database transactions should be kept short as much as possible. Because long-hanging +transactions may keep certain database locks, leading to performance issues or even +triggering a chain reaction of deadlocks.

    6. +
    +
    +
    +

    Be explicit.

    +

    With that said, it is especially important to make everything explicit - all the +connection acquisition, transactions and executions. Anything that will block must be +marked with an await or async with, so that you'll know for sure when a +statement is trying to make any database I/O. Fortunately there's no other way in +asyncio to do this - following the pattern of Twisted, asyncio is already forcing +explicit await for any asynchronous operations.

    +

    Being explicit in other part of the ORM design is also useful for enhancing the quality +of users' code. This has nothing to do with asynchronous, it's not a golden standard or +something with a clear cut either. For example:

    +
      +
    • We could design the ORM model instances to be stateless, so that the users don't have +to learn and worry about maintaining the state of the instances.

    • +
    • There shouldn't be any "buffered operations" which users could "flush" with a single +statement once for all.

    • +
    • The user should just give direct one-off commands and the ORM executes them right away.

    • +
    • Also I think the convenience tooling should be well-balanced, the user doesn't have to +guess or remember what an API means - for example, there're more than one ways to load +a many-to-one relationship, I'd prefer to write the query by myself rather than trying +to remember what "join_without_n_plus_1()" means.

    • +
    +
    +
    +

    Be productive.

    +

    Explicitness and productivity are kind of like two sides of the same coin - more +explicitness means less productive, and more convenience means less explicit. As we are +already using Python, being able to code productively is especially important. For an +asynchronous ORM, is it possible to find a proper balance between the two?

    +

    I think some of the fundamental principals must be explicit, for example the stateless +model and asynchronous yieldings. Then we could add convenient tooling on top of this +foundation as much as it doesn't harm the basic explicitness. The tooling could better +be simple wrappers, grammar sugars or shortcuts for lengthy code, with a proper and +intuitive naming. This is mostly the idea behind GINO's design.

    +

    Additionally, being able to leverage an existing ecosystem is also an important part in +productivity. People could use what they've learned directly, port what they wrote to +the new platform with minimum effort. More importantly - reuse some of the tools from +the ecosystem without having to reinvent the wheel again.

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/genindex.html b/docs/zh/master/genindex.html new file mode 100644 index 0000000..270c3b2 --- /dev/null +++ b/docs/zh/master/genindex.html @@ -0,0 +1 @@ +

    genindex.html

    \ No newline at end of file diff --git a/docs/zh/master/how-to.html b/docs/zh/master/how-to.html new file mode 100644 index 0000000..8759b1f --- /dev/null +++ b/docs/zh/master/how-to.html @@ -0,0 +1,292 @@ + + + + + + + + 进阶用法 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    进阶用法

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/alembic.html b/docs/zh/master/how-to/alembic.html new file mode 100644 index 0000000..7cf3f89 --- /dev/null +++ b/docs/zh/master/how-to/alembic.html @@ -0,0 +1,303 @@ + + + + + + + + 使用 Alembic - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    使用 Alembic

    +

    Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database +Toolkit for Python. It’s also possible to use with GINO.

    +

    To add migrations to project first of all, add alembic as dependency:

    +
    $ pip install --user alembic
    +
    +
    +

    When you need to set up alembic for your project.

    +

    Prepare sample project. We will have a structure:

    +
    alembic_sample/
    +    my_app/
    +        models.py
    +
    +
    +

    Inside models.py define simple DB Model with GINO:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +
    +

    Set up Alembic

    +

    This will need to be done only once. Go to the main folder of your project +alembic_sample and run:

    +
    $ alembic init alembic
    +
    +
    +

    Alembic will create a bunch of files and folders in your project directory. One of them +will be alembic.ini. Open alembic.ini (you can find it in the main project +folder alembic_sample). Now change property sqlalchemy.url = with your DB +credentials. Like this:

    +
    sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    +
    +
    +

    Next go to folder alembic/ and open env.py file. Inside the env.py file you +need to import the db object. In our case db object is db from models +modules. This is a variable that links to your Gino() instance.

    +

    Inside alembic/env.py:

    +
    from main_app.models import db
    +
    +
    +

    And change target_metadata = to:

    +
    target_metadata = db
    +
    +
    +

    That’s it. We finished setting up Alembic for a project.

    +
    +

    注解

    +

    All alembic commands must be run always from the folder that contains the +alembic.ini file.

    +
    +
    +
    +

    Create first migration revision

    +

    Same commands you must run each time when you make some changes in DB Models and want to +apply these changes to your DB Schema.

    +
    $ alembic revision -m "first migration" --autogenerate --head head
    +
    +
    +

    If you have any problems relative to package imports similar to this example:

    +
    File "alembic/env.py", line 7, in <module>
    +    from main_app.models import db
    +ModuleNotFoundError: No module named 'main_app'
    +
    +
    +

    Either install your project locally with pip install -e ., poetry install or +python setup.py develop, or add you package to PYTHONPATH, like this:

    +
    $ export PYTHONPATH=$PYTHONPATH:/full_path/to/alembic_sample
    +
    +
    +

    After the successful run of alembic revision in folder alembic/versions you will +see a file with new migration.

    +
    +
    +

    Apply migration on DB

    +

    Now time to apply migration to DB. It will create tables based on you DB Models.

    +
    $ alembic upgrade head
    +
    +
    +

    Great. Now you apply your first migration. Congratulations!

    +

    Next time, when you will make any changes in DB models just do:

    +
    $ alembic revision -m "your migration description" --autogenerate --head head
    +
    +
    +

    And

    +
    alembic upgrade head
    +
    +
    +

    Full documentation about how to work with Alembic migrations, downgrades and other +things - you can find in official docs https://alembic.sqlalchemy.org

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/bakery.html b/docs/zh/master/how-to/bakery.html new file mode 100644 index 0000000..01a762f --- /dev/null +++ b/docs/zh/master/how-to/bakery.html @@ -0,0 +1,409 @@ + + + + + + + + 预制查询 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    预制查询

    +
    +

    1.1 新版功能.

    +
    +

    Baked queries are used to boost execution performance for constantly-used queries. +Similar to the Baked Queries in SQLAlchemy, GINO could also cache the +object’s construction and string-compilation steps. Furthermore, GINO automatically +manages a prepared statement for each baked query in every active connection in the +pool. Executing baked queries is at least 40% faster than running normal queries, but +you need to bake them before creating the engine.

    +

    GINO provides two approaches for baked queries:

    +
      +
    1. Low-level Bakery API

    2. +
    3. High-level Gino.bake() integration

    4. +
    +
    +

    Use Bakery with Bare Engine

    +

    First, we need a bakery:

    +
    import gino
    +
    +bakery = gino.Bakery()
    +
    +
    +

    Then, let's bake some queries:

    +
    db_time = bakery.bake("SELECT now()")
    +
    +
    +

    Or queries with parameters:

    +
    user_query = bakery.bake("SELECT * FROM users WHERE id = :uid")
    +
    +
    +

    Let's assume we have this users table defined in SQLAlchemy Core:

    +
    import sqlalchemy as sa
    +
    +metadata = sa.MetaData()
    +user_table = sa.Table(
    +    "users", metadata,
    +    sa.Column("id", sa.Integer, primary_key=True),
    +    sa.Column("name", sa.String),
    +)
    +
    +
    +

    Now we can bake a similar query with SQLAlchemy Core:

    +
    user_query = bakery.bake(
    +    sa.select([user_table]).where(user.c.id == sa.bindparam("uid"))
    +)
    +
    +
    +

    These baked queries are usually global, and supposed to be shared across the +application. To run them, we need an engine with the bakery:

    +
    engine = await gino.create_engine("postgresql://localhost/", bakery=bakery)
    +
    +
    +

    By doing so, GINO will bake the queries in the bakery. As new connections are added to +the DB pool, the prepared statements are automatically created behind the scene.

    +

    To execute the baked queries, you could treat the BakedQuery +instances as if they are the queries themselves, for example:

    +
    now = await engine.scalar(db_time)
    +
    +
    +

    Pass in parameter values:

    +
    row = await engine.first(user_query, uid=123)
    +
    +
    +
    +
    +

    Use the Gino Integration

    +

    In a more common scenario, there will be a Gino instance, which has +usually a bind set - either explicitly or by the Web framework extensions:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        ...
    +
    +
    +

    A Bakery is automatically created in the db instance, and fed +to the engine implicitly. You can immediately start to bake queries without further +ado:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +db_time = db.bake("SELECT now()")
    +user_getter = db.bake(User.query.where(User.id == db.bindparam("uid")))
    +
    +
    +

    And the execution is also simplified with the same bind magic:

    +
    async def main():
    +    async with db.with_bind("postgresql://localhost/"):
    +        print(await db_time.scalar())
    +
    +        user: User = await user_getter.first(uid=1)
    +        print(user.name)
    +
    +
    +

    To make things easier, you could even define the baked queries directly on the +model:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +    @db.bake
    +    def getter(cls):
    +        return cls.query.where(cls.id == db.bindparam("uid"))
    +
    +    @classmethod
    +    async def get(cls, uid):
    +        return await cls.getter.one_or_none(uid=uid)
    +
    +
    +

    Here GINO treats the getter() as a declared_attr() with +with_table=True, therefore it takes one positional argument cls for the User +class.

    +
    +
    +

    How to customize loaders?

    +

    If possible, you could bake the additional execution options into the query:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +)
    +
    +
    +

    The bake() method accepts keyword arguments as execution +options to e.g. simplify the example above into:

    +
    user_getter = db.bake(
    +    User.query.where(User.id == db.bindparam("uid")),
    +    loader=User.load(comment="Added by loader."),
    +)
    +
    +
    +

    If the query construction is complex, bake() could also be +used as a decorator:

    +
    @db.bake
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid")).execution_options(
    +        loader=User.load(comment="Added by loader.")
    +    )
    +
    +
    +

    Or with short execution options:

    +
    @db.bake(loader=User.load(comment="Added by loader."))
    +def user_getter():
    +    return User.query.where(User.id == db.bindparam("uid"))
    +
    +
    +

    Meanwhile, it is also possible to override the loader at runtime:

    +
    user: User = await user_getter.load(User).first(uid=1)
    +print(user.name)  # no more comment on user!
    +
    +
    +
    +

    提示

    +

    This override won't affect the baked query - it's used only in this execution.

    +
    +
    +
    +

    What APIs are available on BakedQuery?

    +

    BakedQuery is a GinoExecutor, so it inherited +all the APIs like all(), +first(), one(), +one_or_none(), scalar(), +status(), load(), +timeout(), etc.

    +

    GinoExecutor is actually the chained .gino helper API seen +usually in queries like this:

    +
    user = await User.query.where(User.id == 123).gino.first()
    +
    +
    +

    So a BakedQuery can be seen as a normal query with the .gino +suffix, plus it is directly executable.

    +
    +

    参见

    +

    Please see API document of gino.bakery for more information.

    +
    +
    +
    +

    I don't want the prepared statements.

    +

    If you don't need all the baked queries (m) to create prepared statements for all +the active database connections (n) in the beginning, you could set +prebake=False in the engine initialization to prevent the default initial +m x n prepare calls:

    +
    e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False)
    +
    +
    +

    Or if you're using bind:

    +
    await db.set_bind("postgresql://...", prebake=False)
    +
    +
    +

    This is useful when you're depending on db.gino.create_all() to create the tables, +because the prepared statements can only be created after the table creation.

    +

    The prepared statements will then be created and cached lazily on demand.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/contributing.html b/docs/zh/master/how-to/contributing.html new file mode 100644 index 0000000..bee7cfb --- /dev/null +++ b/docs/zh/master/how-to/contributing.html @@ -0,0 +1,362 @@ + + + + + + + + 贡献 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    贡献

    +

    Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given.

    +

    您可以通过多种方式做出贡献:

    +
    +

    Types of Contributions

    +
    +

    报告错误

    +

    Report bugs at https://github.com/python-gino/gino/issues.

    +

    如果您要报告错误,请包括:

    +
      +
    • 您的操作系统名称和版本。

    • +
    • 有关本地设置的任何详细信息都可能有助于排除故障。

    • +
    • 重现错误的详细步骤。

    • +
    +
    +
    +

    修复错误

    +

    在GitHub issues 查找错误。任何人都可以将标记为“bug”和“help wanted”并且是打开状态的问题修复。

    +
    +
    +

    实现功能

    +

    在GitHub issues 查找功能。任何人都可以将标记为“增强”和“需要帮助”的打开状态的需求实现。

    +
    +
    +

    写文档

    +

    GINO需要更多的使用文档,无论是作为官方GINO文档的一部分,还是文档字符串,甚至是博客,文章等等。

    +
    +
    +

    提交反馈

    +

    The best way to send feedback is to file an issue at https://github.com/python-gino/gino/issues.

    +

    如果您要提出一项功能:

    +
      +
    • 详细解释它是如何工作的。

    • +
    • 为了更容易实现,请保持范围尽可能窄。

    • +
    • Remember that this is a volunteer-driven project, and that contributions +are welcome :)

    • +
    +
    +
    +
    +

    Get Started!

    +

    Ready to contribute? Here's how to set up gino for local development.

    +
      +
    1. Fork the gino repo on GitHub.

    2. +
    3. Clone your fork locally:

      +
      $ git clone git@github.com:your_name_here/gino.git
      +
      +
      +
    4. +
    5. Create a branch for local development:

      +
      $ cd gino/
      +$ git checkout -b name-of-your-bugfix-or-feature
      +
      +
      +
    6. +
    +

    Now you can make your changes locally.

    +
      +
    1. Create virtual environment. Example for virtualenvwrapper:

      +
      $ mkvirtualenv gino
      +
      +
      +
    2. +
    3. Activate the environment and install requirements:

      +
      $ pip install -r requirements_dev.txt
      +
      +
      +
    4. +
    5. When you're done making changes, check that your changes pass syntax checks:

      +
      $ flake8 gino tests
      +
      +
      +
    6. +
    +

    7. And tests (including other Python versions with tox). +For tests you you will need running database server (see "Tips" section below for configuration details):

    +
    $ pytest tests
    +$ tox
    +
    +
    +
      +
    1. For docs run:

      +
      $ make docs
      +
      +
      +
    2. +
    +

    It will build and open up docs in your browser.

    +
      +
    1. Commit your changes and push your branch to GitHub:

      +
      $ git add .
      +$ git commit -m "Your detailed description of your changes."
      +$ git push origin name-of-your-bugfix-or-feature
      +
      +
      +
    2. +
    3. Submit a pull request through the GitHub website.

    4. +
    +
    +
    +

    Pull Request Guidelines

    +

    Before you submit a pull request, check that it meets these guidelines:

    +
      +
    1. The pull request should include tests.

    2. +
    3. If the pull request adds functionality, the docs should be updated. Put +your new functionality into a function with a docstring, and add the +feature to the list in README.rst.

    4. +
    5. The pull request should work for Python 3.6, 3.7, 3.8 and 3.9. Check +https://github.com/python-gino/gino/actions?query=event%3Apull_request +and make sure that the tests pass for all supported Python versions.

    6. +
    +
    +
    +

    Tips

    +

    To run a subset of tests:

    +
    $ py.test -svx tests.test_gino
    +
    +
    +

    By default the tests run against a default installed postgres database. If you +wish to run against a separate database for the tests you can do this by first +creating a new database and user using 'psql' or similar:

    +
    CREATE ROLE gino WITH LOGIN ENCRYPTED PASSWORD 'gino';
    +CREATE DATABASE gino WITH OWNER = gino;
    +
    +
    +

    Then run the tests like so:

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino
    +$ py.test
    +
    +
    +

    Here is an example for db server in docker. Some tests require ssl so you will need to run postgres with ssl enabled. +Terminal 1 (server):

    +
    $ openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
    +$ openssl rsa -in privkey.pem -passin pass:abcd -out server.key
    +$ openssl req -x509 -in server.req -text -key server.key -out server.crt
    +$ chmod 600 server.key
    +$ docker run --name gino_db --rm -it -p 5433:5432 -v "$(pwd)/server.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/server.key:/var/lib/postgresql/server.key:ro" postgres:12-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    +
    +
    +

    Terminal 2 (client):

    +
    $ export DB_USER=gino DB_PASS=gino DB_NAME=gino DB_PORT=5433
    +$ docker exec gino_db psql -U postgres -c "CREATE ROLE $DB_USER WITH LOGIN ENCRYPTED PASSWORD '$DB_PASS'"
    +$ docker exec gino_db psql -U postgres -c "CREATE DATABASE $DB_NAME WITH OWNER = $DB_USER;"
    +$ pytest tests/test_aiohttp.py
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/crud.html b/docs/zh/master/how-to/crud.html new file mode 100644 index 0000000..fd61050 --- /dev/null +++ b/docs/zh/master/how-to/crud.html @@ -0,0 +1,201 @@ + + + + + + + + 增删改查 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    增删改查

    +

    未完待续

    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/extensions.html b/docs/zh/master/how-to/extensions.html new file mode 100644 index 0000000..aab8321 --- /dev/null +++ b/docs/zh/master/how-to/extensions.html @@ -0,0 +1,187 @@ + + + + + + + + Extensions - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    + + +
    +
    + + + + +
    +
    + +
    +
    + + +
    + ZH +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/extensions/sanic.html b/docs/zh/master/how-to/extensions/sanic.html new file mode 100644 index 0000000..0b84306 --- /dev/null +++ b/docs/zh/master/how-to/extensions/sanic.html @@ -0,0 +1,294 @@ + + + + + + + + Sanic Support - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + +
    + ZH +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/extensions/starlette.html b/docs/zh/master/how-to/extensions/starlette.html new file mode 100644 index 0000000..862a5df --- /dev/null +++ b/docs/zh/master/how-to/extensions/starlette.html @@ -0,0 +1,291 @@ + + + + + + + + Starlette Support - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    注解

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + +
    + ZH +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/extensions/tornado.html b/docs/zh/master/how-to/extensions/tornado.html new file mode 100644 index 0000000..0429489 --- /dev/null +++ b/docs/zh/master/how-to/extensions/tornado.html @@ -0,0 +1,175 @@ + + + + + + + + Tornado Support - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    + + +
    + ZH +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/faq.html b/docs/zh/master/how-to/faq.html new file mode 100644 index 0000000..5996941 --- /dev/null +++ b/docs/zh/master/how-to/faq.html @@ -0,0 +1,564 @@ + + + + + + + + 常见问题 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    常见问题

    +
    +

    SQLAlchemy 1.4 supports asyncio, what will GINO be?

    +

    Starting from 1.4, SQLAlchemy will support asyncio. +This is a great news to the SQLAlchemy-based ORMs including GINO, because the users will +have one more option, and many GINO hacks can be eventually cleaned up. SQLAlchemy +achieved both sync and async API with the same code base by encapsulating the use of +greenlet. As an "async" user, you don't +need to worry about its internals in most cases, it's fine to just use its async APIs. +Both SQLAlchemy Core and ORM will be async-compatible, but some ORM features that +involve implicit database accesses are disallowed.

    +

    Given such news, GINO no longer has to maintain its own asyncpg dialect for SQLAlchemy +in future versions (yay!). Still, GINO will focus on the differences and keep a +compatible API. To list some of the different/future features:

    +
      +
    • Contextual Connections (see 引擎与连接)

    • +
    • SQLAlchemy Core-based CRUD models

    • +
    • The GINO Loader system

    • +
    • Async MySQL support

    • +
    • Typing support NEW

    • +
    • Trio support NEW

    • +
    • Execution performance NEW

    • +
    +

    As SQLAlchemy async support is considered in Alpha level, GINO will include SQLAlchemy +1.4 support in GINO 2.0.0-alpha.x releases, while the current GINO 1.0.x and 1.1.x will +remain on SQLAlchemy 1.3. GINO 1.x will receive bug fixes and security updates until +both SQLAlchemy async support and GINO 2.x are stabilized.

    +
    +
    +

    ORM or not ORM?

    +

    GINO does perform the Object-Relational Mapping work under the +Data Mapper Pattern, but +it is just not a traditional ORM. The Objects in GINO are completely stateless +from database - they are pure plain Python objects in memory. Changing their +attribute values does not make them "dirty" - or in a different way of thinking +they are always "dirty". Any access to database must be explicitly executed. +Using GINO is more like making up SQL clauses with Models and Objects, +executing them to make changes in database, or loading data from database and +wrapping the results with Objects again. Objects are just row data containers, +you are still dealing with SQL which is represented by Models and SQLAlchemy +core grammars. Besides GINO can be used in a completely non-ORM way.

    +
    +
    +

    Can I use features of SQLAlchemy ORM?

    +

    SQLAlchemy has two parts:

    +
      +
    • SQLAlchemy Core

    • +
    • SQLAlchemy ORM

    • +
    +

    GINO is built on top of SQLAlchemy Core, so SQLAlchemy ORM won't work in GINO.

    +
    +
    +

    How to join?

    +

    GINO invented none about making up queries, everything for that is inherited +from SQLAlchemy. Therefore you just need to know how to write join in +SQLAlchemy. +Especially, Ádám made some amazing upgrades in +GINO #113 to make join easier, so +that you can use model classes directly as if they are tables in joining:

    +
    results = await User.join(Book).select().gino.all()
    +
    +
    +
    +
    +

    How to connect to database through SSL?

    +

    It depends on the dialect and database driver. For asyncpg, keyword arguments +on asyncpg.connect() are directly +available on create_engine() or db.set_bind(). Therefore, enabling SSL is rather easy:

    +
    engine = await gino.create_engine(..., ssl=True)
    +
    +
    +
    +
    +

    What is aiocontextvars and what does it do?

    +

    It is a partial backport of the new built-in module contextvars introduced in Python +3.7. In Python 3.6, aiocontextvars patches loop.create_task() +to copy context from caller as a workaround to simulate the same behavior. This +is also under discussion in upstream backport project, please read more here: +https://github.com/MagicStack/contextvars/issues/2

    +

    If you are using Python 3.7, then aiocontextvars does nothing at all.

    +
    +

    注解

    +

    This answer is for GINO 0.8 and later, please check earlier versions of +this documentation if you are using GINO 0.7.

    +
    +
    +
    +

    How to define relationships?

    +

    GINO 1.0 or lower doesn't provide relationship definition feature found in typical ORMs. +However, you could always manually define your tables, design your queries and load the +results explicitly in GINO. Please see 加载器与关系 for more information.

    +
    +
    +

    How to define index with multiple columns?

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    first_name = db.Column(db.Unicode())
    +    last_name = db.Column(db.Unicode())
    +
    +    _name_idx = db.Index('index_on_name', 'first_name', 'last_name')
    +
    +
    +

    The _name_idx is not used.

    +
    +
    +

    Is there a django admin interface for GINO?

    +

    Not quite yet, please follow this discussion.

    +
    +
    +

    How to use multiple databases for different users on the fly?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query.

    +

    In order to use multiple databases, you would need multiple +GinoEngine instances. Here's a full example using FastAPI with +lazy engine creation:

    +
    from asyncio import Future
    +from contextvars import ContextVar
    +
    +from fastapi import FastAPI, Request
    +from gino import create_engine
    +from gino.ext.starlette import Gino
    +
    +engines = {}
    +dbname = ContextVar("dbname")
    +
    +
    +class ContextualGino(Gino):
    +    @property
    +    def bind(self):
    +        e = engines.get(dbname.get(""))
    +        if e and e.done():
    +            return e.result()
    +        else:
    +            return self._bind
    +
    +    @bind.setter
    +    def bind(self, val):
    +        self._bind = val
    +
    +
    +app = FastAPI()
    +db = ContextualGino(app)
    +
    +
    +@app.middleware("http")
    +async def lazy_engines(request: Request, call_next):
    +    name = request.query_params.get("db", "postgres")
    +    fut = engines.get(name)
    +    if fut is None:
    +        fut = engines[name] = Future()
    +        try:
    +            engine = await create_engine("postgresql://localhost/" + name)
    +        except Exception as e:
    +            fut.set_exception(e)
    +            del engines[name]
    +            raise
    +        else:
    +            fut.set_result(engine)
    +    await fut
    +    dbname.set(name)
    +    return await call_next(request)
    +
    +
    +@app.get("/")
    +async def get():
    +    return dict(dbname=await db.scalar("SELECT current_database()"))
    +
    +
    +
    +
    +

    How to load complex query results?

    +

    The API doc of gino.loader explains the available loaders, and there're a few +examples in 加载器与关系 too.

    +

    Below is an example with a joined result to load both a GINO model and an integer at the +same time, using a TupleLoader with two sub-loaders - +ModelLoader and ColumnLoader:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Be ware of the tuple in .gino.load((...)).

    +
    +
    +

    How to do bulk or batch insert / update?

    +

    For a simple example, take a model that has one field, "name." In your application you +have a list of names you would like to add to the database:

    +
    new_names = ["Austin", "Ali", "Jeff", "Marissa"]
    +
    +
    +

    To quickly insert the names in one query, first construct a dict with the +{"model_key": "value"} format:

    +
    new_names_dict = [dict(name=new_name) for new_name in new_names]
    +>> [{'name': 'Austin'}, {'name': 'Ali'}, {'name': 'Jeff'}, {'name': 'Marissa'}]
    +
    +
    +

    Finally, run an insert statement on the model:

    +
    await User.insert().gino.all(new_names_dict)
    +
    +
    +
    +
    +

    How to print the executed SQL?

    +

    GINO uses the same approach from SQLAlchemy: create_engine(..., echo=True). +(Or db.set_bind(..., echo=True)) Please see also here.

    +

    If you use any extension, you can also set that in config, by db_echo or DB_ECHO.

    +
    +
    +

    How to run EXISTS SQL?

    +
    await db.scalar(db.exists().where(User.email == email).select())
    +
    +
    +
    +
    +

    How to work with Alembic?

    +

    The fact that Gino is a MetaData is the +key to use Alembic. Just import and set target_metadata = db in Alembic env.py +will do. See 使用 Alembic for more details.

    +
    +
    +

    How to join the same table twice?

    +

    This is the same pattern as described in SQLAlchemy Adjacency List Relationships, where you +have a table with "a foreign key reference to itself", or join the same table more than +once, "to represent hierarchical data in flat tables." We'd need to use +alias(), for example:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.ForeignKey("users.id"))
    +
    +Parent = User.alias()
    +query = User.outerjoin(Parent, User.parent_id == Parent.id).select()
    +users = await query.gino.load(User.load(parent=Parent)).all()
    +
    +
    +
    +
    +

    How to execute raw SQL with parameters?

    +

    Wrap the SQL with text(), and use keyword arguments:

    +
    query = db.text('SELECT * FROM users WHERE id = :id_val')
    +row = await db.first(query, id_val=1)
    +
    +
    +

    You may even load the rows into model instances:

    +
    query = query.execution_options(loader=User)
    +user = await db.first(query, id_val=1)
    +
    +
    +
    +
    +

    Gino engine is not initialized?

    +

    GINO models are linked to a Gino instance, while +Gino has an optional property bind to hold a +GinoEngine instance. So when you are executing:

    +
    user = await User.get(request.user_id)
    +
    +
    +

    The bind is implicitly used to execute the query. If bind is not set before +this, you'll see this error:

    +
    gino.exceptions.UninitializedError: Gino engine is not initialized.
    +
    +
    +

    You could use either:

    +
      +
    • Call set_bind() or with_bind() to set the +bind on the Gino instance.

    • +
    • Use one of the Web framework extensions to set the bind for you in usually the server +start-up hook.

    • +
    • Use explicit bind for each execution, for example:

      +
      engine = await create_engine("...")
      +# ...
      +user = await User.get(request.user_id, bind=engine)
      +
      +
      +
    • +
    +
    +
    +

    How can I do SQL xxxx in GINO?

    +

    GINO uses SQLAlchemy Core queries, so +please check its documentation on how to build queries. The GINO models are simply +wrappers of SQLAlchemy Table instances, and the column +attributes on GINO model classes are just SQLAlchemy Column +instances, you can use them in building your SQLAlchemy Core queries.

    +

    Alternatively, you could always execute the raw SQL directly, see How to execute raw SQL with parameters? above.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/json-props.html b/docs/zh/master/how-to/json-props.html new file mode 100644 index 0000000..2ba6282 --- /dev/null +++ b/docs/zh/master/how-to/json-props.html @@ -0,0 +1,382 @@ + + + + + + + + JSON 扩展属性 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    JSON 扩展属性

    +

    GINO provides additional support to leverage native JSON type in the database as +flexible GINO model fields.

    +
    +

    Quick Start

    +
    from gino import Gino
    +from sqlalchemy.dialects.postgresql import JSONB
    +
    +db = Gino()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +    birthday = db.DateTimeProperty()
    +
    +
    +

    The age and birthday are JSON properties stored in the profile column. You +may use them the same way as a normal GINO model field:

    +
    u = await User.create(name="daisy", age=18)
    +print(u.name, u.age)  # daisy 18
    +
    +
    +
    +

    注解

    +

    profile is the default column name for all JSON properties in a model. If you +need a different column name for some JSON properties, you'll need to specify +explicitly:

    +
    audit_profile = db.Column(JSON, nullable=False, server_default="{}")
    +
    +access_log = db.ArrayProperty(prop_name="audit_profile")
    +abnormal_detected = db.BooleanProperty(prop_name="audit_profile")
    +
    +
    +
    +

    Using JSON properties in queries is supported:

    +
    await User.query.where(User.age > 16).gino.all()
    +
    +
    +

    This is simply translated into a native JSON query like this:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS INTEGER) > $2;  -- ('age', 16)
    +
    +
    +

    Datetime type is very much the same:

    +
    from datetime import datetime
    +
    +await User.query.where(User.birthday > datetime(1990, 1, 1)).gino.all()
    +
    +
    +

    And the generated SQL:

    +
    SELECT users.id, users.name, users.profile
    +FROM users
    +WHERE CAST((users.profile ->> $1) AS TIMESTAMP WITHOUT TIME ZONE) > $2
    +-- ('birthday', datetime.datetime(1990, 1, 1, 0, 0))
    +
    +
    +

    Here's a list of all the supported JSON properties:

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    JSON 扩展属性

    Python type

    JSON type

    Database Type

    StringProperty

    str

    string

    text

    IntegerProperty

    int

    number

    int

    BooleanProperty

    bool

    boolean

    boolean

    DateTimeProperty

    datetime

    string

    text

    ObjectProperty

    dict

    object

    JSON

    ArrayProperty

    list

    array

    JSON

    +
    +
    +

    Hooks

    +

    JSON property provides 2 instance-level hooks to customize the data:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @age.before_set
    +    def age(self, val):
    +        return val - 1
    +
    +    @age.after_get
    +    def age(self, val):
    +        return val + 1
    +
    +u = await User.create(name="daisy", age=18)
    +print(u.name, u.profile, u.age)  # daisy {'age': 17} 18
    +
    +
    +

    And 1 class-level hook to customize the SQLAlchemy expression of the property:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    height = db.JSONProperty()
    +
    +    @height.expression
    +    def height(cls, exp):
    +        return exp.cast(db.Float)  # CAST(profile -> 'height' AS FLOAT)
    +
    +
    +
    +
    +

    Create Index on JSON Properties

    +

    We'll need to use declared_attr() to wait until the model class +is initialized. The rest is very much the same as defining a usual index:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    profile = db.Column(JSONB, nullable=False, server_default="{}")
    +
    +    age = db.IntegerProperty()
    +
    +    @db.declared_attr
    +    def age_idx(cls):
    +        return db.Index("age_idx", cls.age)
    +
    +
    +

    This will lead to the SQL below executed if you run db.gino.create_all():

    +
    CREATE INDEX age_idx ON users (CAST(profile ->> 'age' AS INTEGER));
    +
    +
    +
    +

    警告

    +

    Alembic doesn't support auto-generating revisions for functional indexes yet. You'll +need to manually edit the revision. Please follow this issue for updates.

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/loaders.html b/docs/zh/master/how-to/loaders.html new file mode 100644 index 0000000..9145d51 --- /dev/null +++ b/docs/zh/master/how-to/loaders.html @@ -0,0 +1,645 @@ + + + + + + + + 加载器与关系 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    加载器与关系

    +

    Loaders are used to load database row results into objects.

    +

    GINO doesn't support automated relationship. We insist explicit code style in +asynchronous programming and that conflicts with some usual ORM relationship +patterns. Instead, GINO provides a rich loader system to assist you with manual +relationships through foreign keys or whatever magic. That means, you are +responsible for writing the queries, and GINO could assemble objects for you +from the database result with loaders you defined.

    +
    +

    Model Loader

    +

    The Model Loader is the magic behind GINO CRUD to translate database rows into +model objects. Through CRUD, Model Loaders are assembled internally for you, +you can still use it directly. For example, an ordinary query that returns rows +may look like this:

    +
    query = db.select([User])
    +rows = await query.gino.all()
    +
    +
    +

    In order to load rows into User objects, you can provide an execution +option loader with a new ModelLoader instance:

    +
    from gino.loader import ModelLoader
    +
    +query = db.select([User])
    +query = query.execution_options(loader=ModelLoader(User))
    +users = await query.gino.all()
    +
    +
    +

    The ModelLoader would then load each database row into a +User object. As this is frequently used, GINO made it a shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User.load())
    +users = await query.gino.all()
    +
    +
    +

    And another shortcut:

    +
    query = db.select([User])
    +query = query.execution_options(loader=User)
    +users = await query.gino.all()
    +
    +
    +
    +

    小技巧

    +

    User as loader is transformed into ModelLoader(User) by +Loader.get(), explained later in "Loader +Expression".

    +
    +

    And again:

    +
    query = db.select([User])
    +users = await query.gino.load(User).all()
    +
    +
    +

    This is identical to the normal CRUD query:

    +
    users = await User.query.gino.all()
    +
    +
    +
    +
    +

    Loader Expression

    +

    So Loaders are actually row post-processors, they define how the database rows +should be processed and returned. Other than ModelLoader, +there're also other loaders that could turn the database rows into different +results like based on your definition. GINO provides the Loader Expression +feature for you to easily assemble complex loaders.

    +

    Here is an example using all loaders at once:

    +
    uid, user, sep, cols = await db.select([User]).gino.load(
    +    (
    +        User.id,
    +        User,
    +        '|',
    +        lambda row, ctx: len(row),
    +    )
    +).first()
    +
    +
    +

    Let's check this piece by piece. Overall, the argument of +load() is a tuple. This is interpreted into a +TupleLoader, with each item of the tuple interpreted as a +Loader Expression recursively. That means, it is possible to nest tuples. The +result of a TupleLoader is a tuple.

    +

    Column in Loader Expressions are interpreted as +ColumnLoader. It simply outputs the value of the given +column in the database row. It is your responsibility to select the column in +the query. Please note, ColumnLoader uses the given +column as index to look for the value, not the name of the column. This is a +SQLAlchemy feature to support selecting multiple columns with the same name +from different tables in the same query, especially for ORM. So if you are +using raw textual SQL and wishing to use ColumnLoader, +you'll have to declare columns for the query:

    +
    now = db.Column('time', db.DateTime())
    +result = await db.first(db.text(
    +    'SELECT now() AT TIME ZONE \'UTC\''
    +).columns(
    +    now,
    +).gino.load(
    +    ('now:', now)
    +).query)
    +print(*result)  # now: 2018-04-08 08:23:02.431847
    +
    +
    +

    Let's get back to previous example. The second item in the tuple is a GINO +model class. As we've presented previously, it is interpreted into a +ModelLoader. By default, it loads the values of all the +columns of the give model, and create a new model instance with the values.

    +
    +

    小技巧

    +

    For a complex loader expression, the same row is given to all loaders, so +it doesn't matter User.id is already used before the model loader.

    +
    +

    The last item in the tuple is a callable, it will be called for each row with +two arguments: the first argument is the row itself, while the second is a +contextual value provided by outer loader, we'll get to that later. Similar to +map(), the return value of the call will be the loaded result.

    +

    At last, if none of the above types matches a Loader Expression, it will be +treated as is. Like the '|' separator, it will show up as the third item +in every result returned by the query.

    +
    +
    +

    Many-to-One Relationship

    +

    A classic many-to-one relationship is also known as referencing - the model on +the "many" end keeps a single reference to the model on the "one" end. Although +GINO does not enforce it, usually people use a foreign key for the reference:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +

    So every child has a single parent (or no parent at all), while one parent may +have multiple children. GINO provides an easy way to load children with their +parents:

    +
    async for child in Child.load(parent=Parent).gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    As you may have noticed, Child.load is exactly the shortcut to create +ModelLoader in the very first example. With some +additional keyword arguments, Child.load(parent=Parent) is still a +ModelLoader for Child, the model loader is at the +same time a query builder. It is identical to do this:

    +
    async for child in Child.load(parent=Parent).query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    The query dynamically generates a SQLAlchemy query +based on the knowledge of the loader, and set the loader as execution option at +the same time. The Loader simply forwarded unknown +attributes to its query, that's why .query can +be omitted.

    +

    For ModelLoader, all keyword arguments are interpreted as +subloaders, their results will be set to the attributes of the result model +under the corresponding keys using setattr(). For example, Parent is +interpreted as ModelLoader(Parent) which loads Parent instances, and +Parent instances are set as the parent attribute of the outer Child +instance.

    +
    +

    警告

    +

    If multiple children references the same parent, then each child owns a +unique parent instance with identical values.

    +
    +
    +

    小技巧

    +

    You don't have to define parent attribute on Child. But if you do, +you gain the ability to customize how parent is stored or retrieved. For +example, let's store the parent instance as _parent:

    +
    class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    _parent = None
    +
    +    @property
    +    def parent(self):
    +        return self._parent
    +
    +    @parent.setter
    +    def parent(self, value):
    +        self._parent = value
    +
    +
    +
    +

    The query builder works recursively. For ModelLoader, it +uses LEFT OUTER JOIN to connect the FROM clauses, in order to achieve +many-to-one scenario. The ON clause is determined automatically by foreign +keys. You can also customize the ON clause in case there is no foreign key +(a promise is a promise):

    +
    loader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +async for child in loader.query.gino.iterate():
    +    print(f'Parent of {child.id} is {child.parent.id}')
    +
    +
    +

    And subloaders can be nested:

    +
    subloader = Child.load(parent=Parent.on(Child.parent_id == Parent.id))
    +loader = Grandson.load(parent=subloader.on(Grandson.parent_id == Child.id))
    +
    +
    +

    By now, GINO supports only loading many-to-one joined query. To modify a +relationship, just modify the reference column values.

    +
    +
    +

    Self Referencing

    +
    +

    警告

    +

    Experimental feature.

    +
    +

    Self referencing is usually used to create a tree-like structure. For example:

    +
    class Category(db.Model):
    +    __tablename__ = 'categories'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    +
    +
    +

    In order to load leaf categories with their parents, an alias is needed:

    +
    Parent = Category.alias()
    +
    +
    +

    Then the query would be something like this:

    +
    parents = db.select([Category.parent_id])
    +query = Category.load(parent=Parent.on(
    +    Category.parent_id == Parent.id
    +)).where(
    +    ~Category.id.in_(db.select([Category.alias().parent_id]))
    +)
    +async for c in query.gino.iterate():
    +    print(f'Leaf: {c.id}, Parent: {c.parent.id}')
    +
    +
    +

    The generated SQL looks like this:

    +
    SELECT categories.id, categories.parent_id, categories_1.id, categories_1.parent_id
    +  FROM categories LEFT OUTER JOIN categories AS categories_1
    +    ON categories.parent_id = categories_1.id
    + WHERE categories.id NOT IN (
    +           SELECT categories_2.parent_id
    +             FROM categories AS categories_2
    +       )
    +
    +
    +
    +
    +

    Other Relationships

    +

    GINO 0.7.4 introduced an experimental distinct feature to reduce a result set +with loaders, combining rows under specified conditions. This made it possible +to build one-to-many relationships. Using the same parent-child example above, +we could load distinct parents with all their children:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    @children.setter
    +    def add_child(self, child):
    +        self._children.add(child)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child)).all()
    +
    +
    +

    Here the query is still child outer-joining parent, but the loader is loading +parent instances with distinct IDs only, while storing all their children +through the add_child setter property. In detail for each row, a parent +instance is firstly loaded if no parent instance with the same ID was loaded +previously, or the same parent instance will be reused. Then a child instance +is loaded from the same row, and fed to the possibly reused parent instance by +parent.add_child = new_child.

    +

    Distinct loaders can be nested to load hierarchical data, but it cannot be used +as a query builder to automatically generate queries.

    +

    GINO provides no additional support for one-to-one relationship - the user +should make sure that the query produces rows of distinct instance pairs, and +load them with regular GINO model loaders. When in doubt, the distinct feature +can be used on both sides, but you'll have to manually deal with the conflict +if more than one related instances are found. For example, we could keep only +the last child for each parent:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._child = None
    +
    +    @property
    +    def child(self):
    +        return self._child
    +
    +    @child.setter
    +    def child(self, child):
    +        self._child = child
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +
    +
    +query = Child.outerjoin(Parent).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(child=Child.distinct(Child.id))).all()
    +
    +
    +

    Similarly, you can build many-to-many relationships in the same way:

    +
    class Parent(db.Model):
    +    __tablename__ = 'parents'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._children = set()
    +
    +    @property
    +    def children(self):
    +        return self._children
    +
    +    def add_child(self, child):
    +        self._children.add(child)
    +        child._parents.add(self)
    +
    +
    +class Child(db.Model):
    +    __tablename__ = 'children'
    +    id = db.Column(db.Integer, primary_key=True)
    +
    +    def __init__(self, **kw):
    +        super().__init__(**kw)
    +        self._parents = set()
    +
    +    @property
    +    def parents(self):
    +        return self._parents
    +
    +
    +class ParentXChild(db.Model):
    +    __tablename__ = 'parents_x_children'
    +
    +    parent_id = db.Column(db.Integer, db.ForeignKey('parents.id'))
    +    child_id = db.Column(db.Integer, db.ForeignKey('children.id'))
    +
    +
    +query = Parent.outerjoin(ParentXChild).outerjoin(Child).select()
    +parents = await query.gino.load(
    +    Parent.distinct(Parent.id).load(add_child=Child.distinct(Child.id))).all()
    +
    +
    +

    Likewise, there is for now no way to modify the relationships automatically, +you'll have to manually create, delete or modify ParentXChild instances.

    +
    +
    +

    Advanced Usage of Loaders

    +

    You could use combined loaders flexibly in complex queries - loading +relationships is just one special use case. For example, you could load the count of +visits at the same time of loading each user, by using a tuple loader with two +items - model loader for the user, and column loader for the count:

    +
    import asyncio
    +import random
    +import string
    +
    +import gino
    +from gino.loader import ColumnLoader
    +
    +db = gino.Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    name = db.Column(db.Unicode())
    +
    +
    +class Visit(db.Model):
    +    __tablename__ = 'visits'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    time = db.Column(db.DateTime(), server_default='now()')
    +    user_id = db.Column(db.ForeignKey('users.id'))
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +
    +        for i in range(random.randint(5, 10)):
    +            u = await User.create(
    +                name=''.join(random.choices(string.ascii_letters, k=10)))
    +            for v in range(random.randint(10, 20)):
    +                await Visit.create(user_id=u.id)
    +
    +        visits = db.func.count(Visit.id)
    +        q = db.select([
    +            User,
    +            visits,
    +        ]).select_from(
    +            User.outerjoin(Visit)
    +        ).group_by(
    +            *User,
    +        ).gino.load((User, ColumnLoader(visits)))
    +        async with db.transaction():
    +            async for user, visits in q.iterate():
    +                print(user.name, visits)
    +
    +        await db.gino.drop_all()
    +
    +
    +asyncio.run(main())
    +
    +
    +

    Using alias to get ID-ascending pairs from the same table:

    +
    ua1 = User.alias()
    +ua2 = User.alias()
    +join_query = select([ua1, ua2]).where(ua1.id < ua2.id)
    +loader = ua1.load('id'), ua2.load('id')
    +result = await join_query.gino.load(loader).all()
    +print(result)  # e.g. [(1, 2), (1, 3), (2, 3)]
    +
    +
    +

    Potentially there could be a lot of different use cases of loaders. We'll add +more inspiration here in the future.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/pool.html b/docs/zh/master/how-to/pool.html new file mode 100644 index 0000000..aefa876 --- /dev/null +++ b/docs/zh/master/how-to/pool.html @@ -0,0 +1,223 @@ + + + + + + + + 连接池 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    连接池

    +

    Other than the default connection pool, alternative pools can be used in +their own use cases. +There are options from dialects (currently only +NullPool), and users can define their own pools. +The base class should be Pool.

    +

    To use non-default pools in raw GINO:

    +
    from gino.dialects.asyncpg import NullPool
    +create_engine('postgresql://...', pool_class=NullPool)
    +
    +
    +

    To use non-default pools in extensions (taking Sanic as an example):

    +
    from gino.dialects.asyncpg import NullPool
    +from gino.ext.sanic import Gino
    +
    +app = sanic.Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_KWARGS = dict(
    +    pool_class=NullPool,
    +)
    +db = Gino()
    +db.init_app(app)
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/sanic.html b/docs/zh/master/how-to/sanic.html new file mode 100644 index 0000000..01687fd --- /dev/null +++ b/docs/zh/master/how-to/sanic.html @@ -0,0 +1,280 @@ + + + + + + + + Sanic Support - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + + + +
    +
    + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/schema.html b/docs/zh/master/how-to/schema.html new file mode 100644 index 0000000..0584e9a --- /dev/null +++ b/docs/zh/master/how-to/schema.html @@ -0,0 +1,425 @@ + + + + + + + + 表结构定义 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    表结构定义

    +

    There are 3 ways to declare your database schema to be used with GINO. Because +GINO is built on top of SQLAlchemy core, either way you are actually declaring +SQLAlchemy Table.

    +
    +

    GINO Engine

    +

    This is the minimized way to use GINO - using only +GinoEngine (and GinoConnection +too), everything else are vanilla SQLAlchemy core. This is useful when you have +legacy code written in SQLAlchemy core, in need of porting to asyncio. For new +code please use the other two.

    +

    For example, the table declaration is the same as SQLAlchemy core tutorial:

    +
    from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
    +
    +metadata = MetaData()
    +
    +users = Table(
    +    'users', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('name', String),
    +    Column('fullname', String),
    +)
    +
    +addresses = Table(
    +    'addresses', metadata,
    +
    +    Column('id', Integer, primary_key=True),
    +    Column('user_id', None, ForeignKey('users.id')),
    +    Column('email_address', String, nullable=False)
    +)
    +
    +
    +
    +

    注解

    +

    When using GINO Engine only, it is usually your own business to create the +tables with either create_all() on a +normal non-async SQLAlchemy engine, or using Alembic. However it is still +possible to be done with GINO if it had to:

    +
    import gino
    +from gino.schema import GinoSchemaVisitor
    +
    +async def main():
    +    engine = await gino.create_engine('postgresql://...')
    +    await GinoSchemaVisitor(metadata).create_all(engine)
    +
    +
    +
    +

    Then, construct queries, in SQLAlchemy core too:

    +
    ins = users.insert().values(name='jack', fullname='Jack Jones')
    +
    +
    +

    So far, everything is still in SQLAlchemy. Now let's get connected and execute +the insert:

    +
    async def main():
    +    engine = await gino.create_engine('postgresql://localhost/gino')
    +    conn = await engine.acquire()
    +    await conn.status(ins)
    +    print(await conn.all(users.select()))
    +    # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Here create_engine() creates a GinoEngine, +then acquire() checks out a +GinoConnection, and +status() executes the insert and returns the +status text. This works similarly as SQLAlchemy +execute() - they take the same parameters +but return a bit differently. There are also other similar query APIs:

    +
      +
    • all() returns a list of +RowProxy

    • +
    • first() returns one +RowProxy, or None

    • +
    • one() returns one +RowProxy

    • +
    • one_or_none() returns one +RowProxy, or None

    • +
    • scalar() returns a single value, or +None

    • +
    • iterate() returns an asynchronous iterator +which yields RowProxy

    • +
    +

    Please go to their API for more information.

    +
    +
    +

    GINO Core

    +

    In previous scenario, GinoEngine must not be set to +metadata.bind because it is not a +regular SQLAlchemy Engine thus it won't work correctly. For this, GINO provides +a subclass of MetaData as Gino, +usually instantiated globally under the name of db. It can be used as a +normal MetaData still offering some conveniences:

    +
      +
    • It delegates most public types you can access on sqlalchemy

    • +
    • It works with both normal SQLAlchemy engine and asynchronous GINO engine

    • +
    • It exposes all query APIs on GinoConnection level

    • +
    • It injects two gino extensions on SQLAlchemy query clauses and schema +items, allowing short inline execution like users.select().gino.all()

    • +
    • It is also the entry for the third scenario, see later

    • +
    +

    Then we can achieve previous scenario with less code like this:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +users = db.Table(
    +    'users', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('name', db.String),
    +    db.Column('fullname', db.String),
    +)
    +
    +addresses = db.Table(
    +    'addresses', db,
    +
    +    db.Column('id', db.Integer, primary_key=True),
    +    db.Column('user_id', None, db.ForeignKey('users.id')),
    +    db.Column('email_address', db.String, nullable=False)
    +)
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await users.insert().values(
    +            name='jack',
    +            fullname='Jack Jones',
    +        ).gino.status()
    +        print(await users.select().gino.all())
    +        # Outputs: [(1, 'jack', 'Jack Jones')]
    +
    +
    +

    Similar to SQLAlchemy core and ORM, this is GINO core. All tables and queries +are still made of SQLAlchemy whose rules still apply, but sqlalchemy seems +never imported. This is useful when ORM is unwanted.

    +
    +

    小技巧

    +

    asyncpgsa does the same thing, +but in a conceptually reversed way - instead of having asyncpg work for +SQLAlchemy, it made SQLAlchemy work for asyncpg (GINO used to be in that +way too because GINO is inspired by asyncpgsa). Either way works fine, it's +just a matter of taste of whose API style to use, SQLAlchemy or asyncpg.

    +
    +
    +
    +

    GINO ORM

    +

    If you want to further reduce the length of code, and taking a bit risk of +implicity, welcome to the ORM world. Even though GINO made itself not quite a +traditional ORM by being simple and explict to safely work with asyncio, common +ORM concepts are still valid - a table is a model class, a row is a model +instance. Still the same example rewritten in GINO ORM:

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +    fullname = db.Column(db.String)
    +
    +
    +class Address(db.Model):
    +    __tablename__ = 'addresses'
    +
    +    id = db.Column(db.Integer, primary_key=True)
    +    user_id = db.Column(None, db.ForeignKey('users.id'))
    +    email_address = db.Column(db.String, nullable=False)
    +
    +
    +async def main():
    +    async with db.with_bind('postgresql://localhost/gino'):
    +        await db.gino.create_all()
    +        await User.create(name='jack', fullname='Jack Jones')
    +        print(await User.query.gino.all())
    +        # Outputs: [<User object at 0x10a8ba860>]
    +
    +
    +
    +

    重要

    +

    The __tablename__ is a mandatory field to define a concrete model.

    +
    +

    As you can see, the declaration is pretty much the same as before. Underlying +they are identical, declaring two tables in db. The class style is just +more declarative. Instead of users.c.name, you can now access the column by +User.name. The implicitly created Table is +available at User.__table__ and Address.__table__. You can use anything +that works in GINO core here.

    +
    +

    注解

    +

    Column names can be different as a class property and database column. +For example, name can be declared as +nickname = db.Column('name', db.Unicode(), default='noname'). In this +example, User.nickname is used to access the column, while in database, +the column name is name.

    +

    What's worth mentioning is where raw SQL statements are used, or +TableClause is involved, like User.insert(), the original name is +required to be used, because in this case, GINO has no knowledge about the +mappings.

    +
    +
    +

    小技巧

    +

    db.Model is a dynamically created parent class for your models. It is +associated with the db on initialization, therefore the table is put in +the very db when you declare your model class.

    +
    +

    Things become different when it comes to CRUD. You can use model level methods +to directly create() a model instance, instead of +inserting a new row. Or delete() a model instance +without needing to specify the where clause manually. Query returns model +instances instead of RowProxy, and row values are +directly available as attributes on model instances. See also: +增删改查.

    +

    After all, GinoEngine is always in use. Next let's dig +more into it.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/starlette.html b/docs/zh/master/how-to/starlette.html new file mode 100644 index 0000000..0ec4d49 --- /dev/null +++ b/docs/zh/master/how-to/starlette.html @@ -0,0 +1,277 @@ + + + + + + + + Starlette Support - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    注解

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + + + +
    +
    + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/tornado.html b/docs/zh/master/how-to/tornado.html new file mode 100644 index 0000000..7546ee3 --- /dev/null +++ b/docs/zh/master/how-to/tornado.html @@ -0,0 +1,161 @@ + + + + + + + + Tornado Support - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +
    + +
    +
    + + + + +
    +
    + +
    +
    + + + + +
    +
    + + + + + \ No newline at end of file diff --git a/docs/zh/master/how-to/transaction.html b/docs/zh/master/how-to/transaction.html new file mode 100644 index 0000000..6ff9bf3 --- /dev/null +++ b/docs/zh/master/how-to/transaction.html @@ -0,0 +1,309 @@ + + + + + + + + 数据库事务 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    数据库事务

    +

    It is crucial to correctly manage transactions in an asynchronous program, +because you never know how much time an await will actually wait for, it +will cause disasters if transactions are on hold for too long. GINO enforces +explicit transaction management to help dealing with it.

    +
    +

    Basic usage

    +

    Transactions belong to GinoConnection. The most common +way to use transactions is through an async with statement:

    +
    async with connection.transaction() as tx:
    +    await connection.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    This guarantees a transaction is opened when entering the async with block, +and closed when exiting the block - committed if exits normally, or rolled back +by exception. The underlying transaction instance from the database driver is +available at raw_transaction, but in +most cases you don't need to touch it.

    +

    GINO provides two convenient shortcuts to end the transaction early:

    + +

    They will raise an internal exception to correspondingly commit or rollback the +transaction, thus the code within the async with block after +raise_commit() or +raise_rollback() is skipped. The +internal exception is inherited from BaseException so that normal try +... except Exception block can't trap it. This exception stops propagating at +the end of async with block, so you don't need to worry about handling it.

    +

    Transactions can also be started on a GinoEngine:

    +
    async with engine.transaction() as tx:
    +    await engine.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +

    Here a GinoConnection is borrowed implicitly before +entering the transaction, and guaranteed to be returned after transaction is +done. The GinoConnection instance is accessible at +tx.connection. Other than +that, everything else is the same.

    +
    +

    重要

    +

    The implicit connection is by default borrowed with reuse=True. That +means using transaction() of +GinoEngine within a connection context is the same as +calling transaction() of the current +connection without having to reference it, no separate connection shall be +created.

    +
    +

    Similarly, if your Gino instance has a bind, you may also do +the same on it:

    +
    async with db.transaction() as tx:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +
    +
    +
    +
    +

    Nested Transactions

    +

    Transactions can be nested, nested transaction will create a savepoint as for +now on asyncpg. A similar example from asyncpg doc:

    +
    async with connection.transaction() as tx1:
    +    await connection.status('CREATE TABLE mytab (a int)')
    +
    +    # Create a nested transaction:
    +    async with connection.transaction() as tx2:
    +        await connection.status('INSERT INTO mytab (a) VALUES (1), (2)')
    +        # Rollback the nested transaction:
    +        tx2.raise_rollback()
    +
    +    # Because the nested transaction was rolled back, there
    +    # will be nothing in `mytab`.
    +    assert await connection.all('SELECT a FROM mytab') == []
    +
    +
    +

    As you can see, the raise_rollback() +breaks only the async with block of the specified tx2, the outer +transaction tx1 just continued. What if we break the outer transaction from +within the inner transaction? The inner transaction context won't trap the +internal exception because it recognizes the exception is not created upon +itself. Instead, the inner transaction context only follows the behavior to +either commit or rollback, and lets the exception propagate.

    +

    Because of the default reusing behavior, transactions on engine or db +follows the same nesting rules. Please see +GinoTransaction for more information.

    +
    +
    +

    Manual Control

    +

    Other than using async with, you can also manually control the +transaction:

    +
    tx = await connection.transaction()
    +try:
    +    await db.status('INSERT INTO mytable VALUES(1, 2, 3)')
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    You can't use raise_commit() or +raise_rollback() here, similarly it is +prohibited to use commit() and +rollback() in an async with block.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/index.html b/docs/zh/master/index.html new file mode 100644 index 0000000..5da57c1 --- /dev/null +++ b/docs/zh/master/index.html @@ -0,0 +1,235 @@ + + + + + + + + 欢迎来到 GINO 的文档! - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    欢迎来到 GINO 的文档!

    +PyPI Release Version +GitHub Workflow Status for tests +Codacy coverage +Dependabot +

    GINO 递归定义为 GINO Is Not ORM,是一个基于 asyncioSQLAlchemy core 的轻量级异步 Python ORM 框架,目前(2020 年初)仅支持 asyncpg 一种引擎。

    + + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/objects.inv b/docs/zh/master/objects.inv new file mode 100644 index 0000000..bc085e1 Binary files /dev/null and b/docs/zh/master/objects.inv differ diff --git a/docs/zh/master/py-modindex.html b/docs/zh/master/py-modindex.html new file mode 100644 index 0000000..18dcbf9 --- /dev/null +++ b/docs/zh/master/py-modindex.html @@ -0,0 +1 @@ +

    domainindex.html

    \ No newline at end of file diff --git a/docs/zh/master/reference.html b/docs/zh/master/reference.html new file mode 100644 index 0000000..11524e6 --- /dev/null +++ b/docs/zh/master/reference.html @@ -0,0 +1,342 @@ + + + + + + + + 参考手册 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    参考手册

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api.html b/docs/zh/master/reference/api.html new file mode 100644 index 0000000..5e6693a --- /dev/null +++ b/docs/zh/master/reference/api.html @@ -0,0 +1,248 @@ + + + + + + + + API 参考 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.aiocontextvars.html b/docs/zh/master/reference/api/gino.aiocontextvars.html new file mode 100644 index 0000000..870f296 --- /dev/null +++ b/docs/zh/master/reference/api/gino.aiocontextvars.html @@ -0,0 +1,222 @@ + + + + + + + + gino.aiocontextvars module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.aiocontextvars module

    +
    +
    +gino.aiocontextvars.patch_asyncio()
    +

    Patches asyncio to support contextvars.

    +

    This is automatically called when gino is imported. If Python version is 3.7 +or greater, this function is a no-op.

    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.api.html b/docs/zh/master/reference/api/gino.api.html new file mode 100644 index 0000000..7fab643 --- /dev/null +++ b/docs/zh/master/reference/api/gino.api.html @@ -0,0 +1,627 @@ + + + + + + + + gino.api module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.api module

    +
    +
    +class gino.api.Gino(bind=None, model_classes=None, query_ext=True, schema_ext=True, ext=True, **kwargs)
    +

    基类:sqlalchemy.sql.schema.MetaData

    +

    All-in-one API class of GINO, providing several shortcuts.

    +

    This class is a subclass of SQLAlchemy +MetaData, therefore its instances can be used +as a normal MetaData object, e.g. used in +Alembic. In usual cases, you would +want to define one global Gino instance, usually under the name +of db, representing the database used in your application.

    +

    You may define tables in the official way +SQLAlchemy core recommended, but more often in GINO we define model classes +with db.Model as their parent class to represent tables, for its +objective interface and CRUD operations. Please read 增删改查 +for more information.

    +

    For convenience, Gino instance delegated all properties publicly +exposed by sqlalchemy, so that you can define tables / models +without importing sqlalchemy:

    +
    id = db.Column(db.BigInteger(), primary_key=True)
    +
    +
    +

    Similar to MetaData, a Gino object +can bind to a GinoEngine instance, hereby allowing +"implicit execution" through the gino +extension on Executable or +SchemaItem constructs:

    +
    await User.query.gino.first()
    +await db.gino.create_all()
    +
    +
    +

    Differently, GINO encourages the use of implicit execution and manages +transactional context correctly.

    +

    Binding to a connection object is not supported.

    +

    To set a bind property, you can simply set your +GinoEngine object on db.bind, or +set it to None to unbind. However, the creation of engine usually +happens at the same time. Therefore, GINO provided several convenient ways +doing so:

    +
      +
    1. with_bind() returning an asynchronous context manager:

      +
      async with db.with_bind('postgresql://...') as engine:
      +
      +
      +
    2. +
    3. set_bind() and pop_bind():

      +
      engine = await db.set_bind('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    4. +
    5. Directly await on Gino instance:

      +
      db = await gino.Gino('postgresql://...')
      +await db.pop_bind().close()
      +
      +
      +
    6. +
    +
    +

    注解

    +

    SQLAlchemy allows creating the engine by:

    +
    metadata.bind = 'postgresql://...'
    +
    +
    +

    While in GINO this only sets a string to bind, because +creating an engine requires await, which is exactly what +set_bind() does.

    +
    +

    At last, Gino delegates all query APIs on the bound +GinoEngine.

    +
    +
    +property Model
    +

    Declarative base class for models, subclass of +gino.declarative.Model. Defining subclasses of this class will +result new tables added to this Gino metadata.

    +
    + +
    +
    +acquire(*args, **kwargs)
    +

    A delegate of GinoEngine.acquire().

    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.all().

    +
    + +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    A delegate of Bakery.bake().

    +
    +

    1.1 新版功能.

    +
    +
    + +
    +
    +property bakery
    +

    The bundled Bakery instance.

    +
    +

    1.1 新版功能.

    +
    +
    + +
    +
    +property bind
    +

    An GinoEngine to which this Gino is bound.

    +

    This is a simple property with no getter or setter hook - what you set +is what you get. To achieve the same result as it is in SQLAlchemy - +setting a string or URL and getting an +engine instance, use set_bind() (or await on this +Gino object after setting a string or +URL).

    +
    + +
    +
    +compile(elem, *multiparams, **params)
    +

    A delegate of GinoEngine.compile().

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.first().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.iterate().

    +
    + +
    +
    +model_base_classes = (<class 'gino.crud.CRUDModel'>,)
    +

    Overridable default model classes to build the Model.

    +

    Default is (CRUDModel, ).

    +
    + +
    +
    +no_delegate = {'create_engine', 'engine_from_config'}
    +

    A set of symbols from sqlalchemy which is not delegated by +Gino.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.one_or_none().

    +
    + +
    +
    +pop_bind()
    +

    Unbind self, and return the bound engine.

    +

    This is usually used in a chained call to close the engine:

    +
    await db.pop_bind().close()
    +
    +
    +
    +
    返回
    +

    GinoEngine or None if self is not bound.

    +
    +
    +
    + +
    +
    +query_executor
    +

    The overridable gino extension class on +Executable.

    +

    This class will be set as the getter method of the property gino on +Executable and its subclasses, if +ext and query_ext arguments are both True. Default is +GinoExecutor.

    +

    alias of gino.api.GinoExecutor

    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.scalar().

    +
    + +
    +
    +schema_visitor
    +

    alias of gino.schema.GinoSchemaVisitor

    +
    + +
    +
    +async set_bind(bind, loop=None, **kwargs)
    +

    Bind self to the given GinoEngine and return it.

    +

    If the given bind is a string or +URL, all arguments will be sent to +create_engine() to create a new engine, and return it.

    +
    +
    返回
    +

    GinoEngine

    +
    +
    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    A delegate of GinoEngine.status().

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    A delegate of GinoEngine.transaction().

    +
    + +
    +
    +with_bind(bind, loop=None, **kwargs)
    +

    Shortcut for set_bind() and pop_bind() plus closing engine.

    +

    This method accepts the same arguments of +create_engine(). This allows inline creating an engine and +binding self on enter, and unbinding self and closing the engine on +exit:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # play with engine
    +
    +
    +
    +
    返回
    +

    An asynchronous context manager.

    +
    +
    +
    + +
    + +
    +
    +class gino.api.GinoExecutor(query)
    +

    基类:object

    +

    The default gino extension on +Executable constructs for implicit +execution.

    +

    Instances of this class are created when visiting the gino property of +Executable instances (also referred as +queries or clause elements), for example:

    +
    await User.query.gino.first()
    +
    +
    +

    This allows GINO to add the asynchronous query APIs (all(), +first(), one(), one_or_none(), scalar(), +status(), iterate()) to SQLAlchemy query clauses without +messing up with existing synchronous ones. +Calling these asynchronous query APIs has the same restriction - the +relevant metadata (the Gino instance) must be bound to an engine, +or an AttributeError will be raised.

    +
    +

    注解

    +

    Executable clause elements that are completely irrelevant with any +table - for example db.select([db.text('now()')]) - has no +metadata, hence no engine. Therefore, this will always fail:

    +
    await db.select([db.text('now()')]).gino.scalar()
    +
    +
    +

    You should use conn.scalar(), +engine.scalar() or even +db.scalar() in this case.

    +
    +
    +
    +async all(*multiparams, **params)
    +

    Returns engine.all() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +execution_options(**options)
    +

    Set execution options to this query in a chaining call.

    +

    Read execution_options() for more +information.

    +
    +
    参数
    +

    options -- Multiple execution options.

    +
    +
    +
    +

    1.1 新版功能.

    +
    +
    + +
    +
    +async first(*multiparams, **params)
    +

    Returns engine.first() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +iterate(*multiparams, **params)
    +

    Returns engine.iterate() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +load(value)
    +

    Shortcut to set execution option loader in a chaining call.

    +

    For example to load Book instances with their authors:

    +
    query = Book.join(User).select()
    +books = await query.gino.load(Book.load(author=User)).all()
    +
    +
    +

    Read execution_options() for more +information.

    +
    + +
    +
    +model(model)
    +

    Shortcut to set execution option model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async one(*multiparams, **params)
    +

    Returns engine.one() with this query +as the first argument, and other arguments followed, where engine +is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async one_or_none(*multiparams, **params)
    +

    Returns engine.one_or_none() +with this query as the first argument, and other arguments followed, +where engine is the GinoEngine to which the +metadata (Gino) is bound, while metadata is found in this +query.

    +
    + +
    +
    +property query
    +

    Get back the chained Executable.

    +

    In a chained query calls, occasionally the previous query clause is +needed after a .gino. chain, you can use .query. to resume the +chain back. For example:

    +
    await User.query.gino.model(FOUser).query.where(...).gino.all()
    +
    +
    +
    + +
    +
    +return_model(switch)
    +

    Shortcut to set execution option return_model in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    +
    +async scalar(*multiparams, **params)
    +

    Returns engine.scalar() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +async status(*multiparams, **params)
    +

    Returns engine.status() with this +query as the first argument, and other arguments followed, where +engine is the GinoEngine to which the metadata +(Gino) is bound, while metadata is found in this query.

    +
    + +
    +
    +timeout(timeout)
    +

    Shortcut to set execution option timeout in a chaining call.

    +

    Read execution_options() for more +information.

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.bakery.html b/docs/zh/master/reference/api/gino.bakery.html new file mode 100644 index 0000000..73d69f8 --- /dev/null +++ b/docs/zh/master/reference/api/gino.bakery.html @@ -0,0 +1,329 @@ + + + + + + + + gino.bakery module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.bakery module

    +
    +
    +class gino.bakery.BakedQuery(elem, metadata, hash_=None)
    +

    基类:gino.api.GinoExecutor

    +

    Represents a pre-compiled and possibly prepared query for faster execution.

    +

    BakedQuery is created by Bakery.bake(), and can be executed by +GinoEngine or GinoConnection. If there +is a proper bind in the baked query, contextual execution APIs inherited from +GinoExecutor can also be used.

    +
    +

    1.1 新版功能.

    +
    +
    +
    +property bind
    +

    Internal API to provide a proper bind if found.

    +
    + +
    +
    +property compiled_sql
    +

    Internal API to get the SQLAlchemy compiled sql context.

    +
    + +
    +
    +execution_options(**kwargs)
    +

    Set execution options on a shadow query of this baked query.

    +

    The execution options set in this method won't affect the execution options in +the baked query.

    +

    Read execution_options() for more +information.

    +
    +
    参数
    +

    options -- Multiple execution options.

    +
    +
    返回
    +

    A shadow of the baked query with new execution options but still +functions as a baked query.

    +
    +
    +
    + +
    +
    +get(_)
    +

    Internal API to get the compiled_sql.

    +
    +
    参数
    +

    _ -- Ignored.

    +
    +
    +
    + +
    +
    +property query
    +

    Internal API to get the query instance before compilation.

    +
    + +
    +
    +property sql
    +

    Internal API to get the compiled raw SQL.

    +
    + +
    + +
    +
    +class gino.bakery.Bakery
    +

    基类:object

    +

    Factory and warehouse of baked queries.

    +

    You may provide a bakery to a GinoEngine during creation as +the bakery keyword argument, and the engine will bake the queries and create +corresponding prepared statements for each of the connections in the pool.

    +

    A Gino instance has a built-in bakery, +it's automatically given to the engine during set_bind() or +with_bind().

    +
    +

    1.1 新版功能.

    +
    +
    +
    +bake(func_or_elem=None, **execution_options)
    +

    Bake a query.

    +

    You can bake raw SQL strings or SQLAlchemy Core query instances. This method +adds the given query into a queue in the bakery, and bakes it only when the +bakery is set to an GinoEngine from which the bakery could +learn about the SQL dialect and compile the queries into SQL. Once done, the +bakery is "closed", you can neither give it to another engine, nor use it to +bake more queries.

    +
    +
    参数
    +
      +
    • func_or_elem -- A str or a SQLAlchemy Core query instance, or a +function that returns such results.

    • +
    • execution_options -- Shortcut to add SQLAlchemy execution options to the +query.

    • +
    +
    +
    返回
    +

    A BakedQuery instance.

    +
    +
    +
    + +
    +
    +query_cls
    +

    alias of gino.bakery.BakedQuery

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.crud.html b/docs/zh/master/reference/api/gino.crud.html new file mode 100644 index 0000000..84f0a15 --- /dev/null +++ b/docs/zh/master/reference/api/gino.crud.html @@ -0,0 +1,659 @@ + + + + + + + + gino.crud module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.crud module

    +
    +
    +class gino.crud.Alias(model, *args, **kwargs)
    +

    基类:object

    +

    Experimental proxy for table alias on model.

    +
    +
    +distinct(*columns)
    +
    + +
    +
    +load(*column_names, **relationships)
    +
    + +
    +
    +on(on_clause)
    +
    + +
    + +
    +
    +class gino.crud.CRUDModel(**values)
    +

    基类:gino.declarative.Model

    +

    The base class for models with CRUD support.

    +

    Don't inherit from this class directly, because it has no metadata. Use +db.Model instead.

    +
    +
    +classmethod alias(*args, **kwargs)
    +

    Experimental proxy for table alias on model.

    +
    + +
    +
    +append_where_primary_key(q)
    +

    Append where clause to locate this model instance by primary on the +given query, and return the new query.

    +

    This is mostly used internally in GINO, but also available for such +usage:

    +
    await user.append_where_primary_key(User.query).gino.first()
    +
    +
    +

    which is identical to:

    +
    await user.query.gino.first()
    +
    +
    +
    +

    0.7.6 版后已移除: Use lookup() instead.

    +
    +
    + +
    +
    +create(bind=None, timeout=<object object>, **values)
    +

    This create behaves a bit different on model classes compared to model +instances.

    +

    On model classes, create will create a new model instance and insert +it into database. On model instances, create will just insert the +instance into the database.

    +

    Under the hood create() uses INSERT ... RETURNING ... to +create the new model instance and load it with database default data if +not specified.

    +

    Some examples:

    +
    user1 = await User.create(name='fantix', age=32)
    +user2 = User(name='gino', age=42)
    +await user2.create()
    +
    +
    +
    +
    参数
    +
      +
    • bind -- A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    • values -- Keyword arguments are pairs of attribute names and their +initial values. Only available when called on a model class.

    • +
    +
    +
    返回
    +

    The instance of this model class (newly created or existing).

    +
    +
    +
    + +
    +
    +delete
    +

    Similar to update(), this delete is also different on model +classes than on model instances.

    +

    On model classes delete is an attribute of type +Delete for massive deletes, for +example:

    +
    await User.delete.where(User.enabled.is_(False)).gino.status()
    +
    +
    +

    Similarly you can add a returning() +clause to the query and it shall return the deleted rows as model objects.

    +

    And on model instances, delete() is a method to remove the +corresponding row in the database of this model instance. and returns the +status returned from the database:

    +
    print(await user.delete())  # e.g. prints DELETE 1
    +
    +
    +
    +

    注解

    +

    delete() only removes the row from database, it does not affect the +current model instance.

    +
    +
    +
    参数
    +
      +
    • bind -- An optional GinoEngine if current +metadata (Gino) has no bound engine, or specifying a +different GinoEngine to execute the DELETE.

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    +
    + +
    +
    +classmethod distinct(*columns)
    +

    Experimental loader feature to yield only distinct instances by given +columns.

    +
    + +
    +
    +async classmethod get(ident, bind=None, timeout=<object object>)
    +

    Get an instance of this model class by primary key.

    +

    For example:

    +
    user = await User.get(request.args.get('user_id'))
    +
    +
    +
    +
    参数
    +
      +
    • ident -- Value of the primary key. For composite primary keys this +should be a tuple of values for all keys in database order, or a dict +of names (or position numbers in database order starting from zero) +of all primary keys to their values.

    • +
    • bind -- A GinoEngine to execute the +INSERT statement with, or None (default) to use the bound +engine on the metadata (Gino).

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    返回
    +

    An instance of this model class, or None if no such row.

    +
    +
    +
    + +
    +
    +classmethod in_query(query)
    +

    Convenient method to get a Model object when using subqueries.

    +

    Though with filters and aggregations, subqueries often return same +columns as the original table, but SQLAlchemy could not recognize them +as the columns are in subqueries, so technically they're columns in the +new "table".

    +

    With this method, the columns are loaded into the original models when +being used in subqueries. For example:

    +
    query = query.alias('users')
    +MyUser = User.in_query(query)
    +
    +loader = MyUser.distinct(User1.id).load()
    +users = await query.gino.load(loader).all()
    +
    +
    +
    + +
    +
    +classmethod load(*column_names, **relationships)
    +

    Populates a loader.Loader instance to be used by the +loader execution option in order to customize the loading behavior +to load specified fields into instances of this model.

    +

    The basic usage of this method is to provide the loader execution +option (if you are looking for reloading +the instance from database, check get() or query) for a +given query.

    +

    This method takes both positional arguments and keyword arguments with +very different meanings. The positional arguments should be column +names as strings, specifying only these columns should be loaded into +the model instance (other values are discarded even if they are +retrieved from database). Meanwhile, the keyword arguments should be +loaders for instance attributes. For example:

    +
    u = await User.query.gino.load(User.load('id', 'name')).first()
    +
    +
    +
    +

    小技巧

    +

    gino.load is a shortcut for setting the execution option +loader.

    +
    +

    This will populate a User instance with only id and name +values, all the rest are simply None even if the query actually +returned all the column values.

    +
    q = User.join(Team).select()
    +u = await q.gino.load(User.load(team=Team)).first()
    +
    +
    +

    This will load two instances of model User and Team, returning +the User instance with u.team set to the Team instance.

    +

    Both positional and keyword arguments can be used ath the same time. If +they are both omitted, like Team.load(), it is equivalent to just +Team as a loader.

    +

    Additionally, a loader.Loader instance can also be used to +generate queries, as its structure is usually the same as the query:

    +
    u = await User.load(team=Team).query.gino.first()
    +
    +
    +

    This generates a query like this:

    +
    SELECT users.xxx, ..., teams.xxx, ...
    +  FROM users LEFT JOIN teams
    +    ON ...
    +
    +
    +

    The Loader delegates attributes on the query, so +.query can be omitted. The LEFT JOIN is built-in behavior, +while the ON clause is generated based on foreign key. If there is +no foreign key, or the condition should be customized, you can use +this:

    +
    u = await User.load(
    +    team=Team.on(User.team_id == Team.id)).gino.first()
    +
    +
    +

    And you can use both load() and on() at the same time +in a chain, in whatever order suits you.

    + +
    + +
    +
    +lookup()
    +

    Generate where-clause expression to locate this model instance.

    +

    By default this method uses current values of all primary keys, and you +can override it to behave differently. Most instance-level CRUD +operations depend on this method internally. Particularly while +lookup() is called in update(), the where condition is +used in UpdateRequest.apply(), so that queries like UPDATE ... +SET id = NEW WHERE id = OLD could work correctly.

    +
    +
    返回
    +

    +
    +
    +
    +

    0.7.6 新版功能.

    +
    +
    + +
    +
    +classmethod none_as_none(enabled=True)
    +
    + +
    +
    +classmethod on(on_clause)
    +

    Customize the on-clause for the auto-generated outer join query.

    +
    +

    注解

    +

    This has no effect when provided as the loader execution option +for a given query.

    +
    +
    +

    参见

    +

    load()

    +
    +
    + +
    +
    +query
    +

    Get a SQLAlchemy query clause of the table behind this model. This equals +to sqlalchemy.select([self.__table__]). If this attribute is retrieved on a +model instance, then a where clause to locate this instance by its primary +key is appended to the returning query clause. This model type is set as +the execution option model in the returning clause, so by default the +query yields instances of this model instead of database rows.

    +
    + +
    +
    +select()
    +

    Build a query to retrieve only specified columns from this table.

    +

    This method accepts positional string arguments as names of attributes to +retrieve, and returns a Select for +query. The returning query object is always set with two execution options:

    +
      +
    1. model is set to this model type

    2. +
    3. return_model is set to False

    4. +
    +

    So that by default it always return rows instead of model instances, while +column types can be inferred correctly by the model option.

    +

    For example:

    +
    async for row in User.select('id', 'name').gino.iterate():
    +    print(row['id'], row['name'])
    +
    +
    +

    If select() is invoked on a model instance, then a WHERE clause +to locate this instance by its primary key is appended to the returning +query clause. This is useful when you want to retrieve a latest value of a +field on current model instance from database:

    +
    db_age = await user.select('age').gino.scalar()
    +
    +
    + +
    + +
    +
    +to_dict()
    +

    Convenient method to generate a dict from this model instance.

    +

    Keys will be attribute names, while values are loaded from memory (not +from database). If there are JSONProperty +attributes in this model, their source JSON field will not be included +in the returning dict - instead the JSON attributes will be.

    +
    +

    参见

    +

    json_support

    +
    +
    + +
    +
    +update
    +

    This update behaves quite different on model classes rather than model +instances.

    +

    On model classes, update is an attribute of type +Update for massive updates, for +example:

    +
    await User.update.values(enabled=True).where(...).gino.status()
    +
    +
    +

    Like query, the update query also has the model execution +option of this model, so if you use the +returning() clause, the query shall +return model objects.

    +

    However on model instances, update() is a method which accepts keyword +arguments only and returns an UpdateRequest to update this single +model instance. The keyword arguments are pairs of attribute names and new +values. This is the same as UpdateRequest.update(), feel free to +read more about it. A normal usage example would be like this:

    +
    await user.update(name='new name', age=32).apply()
    +
    +
    +

    Here, the await ... apply() executes the +actual UPDATE SQL in the database, while user.update() only makes +changes in the memory, and collect all changes into an +UpdateRequest instance.

    +
    + +
    + +
    +
    +class gino.crud.QueryModel
    +

    基类:type

    +

    Metaclass of Model classes used for subqueries.

    +
    + +
    +
    +class gino.crud.UpdateRequest(instance: gino.crud.CRUDModel)
    +

    基类:object

    +

    A collection of attributes and their new values to update on one model +instance.

    +

    UpdateRequest instances are created by CRUDModel.update, +don't instantiate manually unless required. Every UpdateRequest +instance is bound to one model instance, all updates are for that one +specific model instance and its database row.

    +
    +
    +async apply(bind=None, timeout=<object object>)
    +

    Apply pending updates into database by executing an UPDATE SQL.

    +
    +
    参数
    +
      +
    • bind -- A GinoEngine to execute the SQL, or +None (default) to use the bound engine in the metadata.

    • +
    • timeout -- Seconds to wait for the database to finish executing, +None for wait forever. By default it will use the timeout +execution option value if unspecified.

    • +
    +
    +
    返回
    +

    self for chaining calls.

    +
    +
    +
    + +
    +
    +update(**values)
    +

    Set given attributes on the bound model instance, and add them into +the update collections for apply().

    +

    Given keyword-only arguments are pairs of attribute names and values to +update. This is not a coroutine, calling update() will have +instant effect on the bound model instance - its in-memory values will +be updated immediately. Therefore this can be used individually as a +shortcut to update several attributes in a batch:

    +
    user.update(age=32, disabled=True)
    +
    +
    +

    update() returns self for chaining calls to either +apply() or another update(). If one attribute is updated +several times by the same UpdateRequest, then only the last +value is remembered for apply().

    +

    Updated values can be SQLAlchemy expressions, for example an atomic +increment for user balance looks like this:

    +
    await user.update(balance=User.balance + 100).apply()
    +
    +
    +
    +

    注解

    +

    Expression values will not affect the in-memory attribute value on +update() before apply(), because it has no knowledge +of the latest value in the database. After apply() the new +value will be automatically reloaded from database with +RETURNING clause.

    +
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.declarative.html b/docs/zh/master/reference/api/gino.declarative.html new file mode 100644 index 0000000..e208d9a --- /dev/null +++ b/docs/zh/master/reference/api/gino.declarative.html @@ -0,0 +1,407 @@ + + + + + + + + gino.declarative module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.declarative module

    +
    +
    +class gino.declarative.ColumnAttribute(prop_name, column)
    +

    基类:object

    +

    The type of the column wrapper attributes on GINO models.

    +

    This is the core utility to enable GINO models so that:

    +
      +
    • Accessing a column attribute on a model class returns the column itself

    • +
    • Accessing a column attribute on a model instance returns the value for that column

    • +
    +

    This utility is customizable by defining __attr_factory__ in the model class.

    +
    + +
    +
    +class gino.declarative.InvertDict(*args, **kwargs)
    +

    基类:dict

    +

    A custom dict that allows getting keys by values.

    +

    Used internally by Model.

    +
    +
    +invert_get(value, default=None)
    +

    Get key by value.

    +
    +
    参数
    +
      +
    • value -- A value in this dict.

    • +
    • default -- If specified value doesn't exist, return default.

    • +
    +
    +
    返回
    +

    The corresponding key if the value is found, or default otherwise.

    +
    +
    +
    + +
    + +
    +
    +class gino.declarative.Model
    +

    基类:object

    +

    The base class of GINO models.

    +

    This is not supposed to be sub-classed directly, declarative_base() should +be used instead to generate a base model class with a given +MetaData. By defining subclasses of a model, instances +of sqlalchemy.schema.Table will be created and added to the bound +MetaData. The columns of the +Table instance are defined as +Column attributes:

    +
    from sqlalchemy import MetaData, Column, Integer, String
    +from gino.declarative import declarative_base
    +
    +Model = declarative_base()
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = Column(Integer(), primary_key=True)
    +    name = Column(String())
    +
    +
    +

    The name of the columns are automatically set using the attribute name.

    +

    An instance of a model will maintain a memory storage for values of all the defined +column attributes. You can access these values by the same attribute name, or update +with new values, just like normal Python objects:

    +
    u = User()
    +assert u.name is None
    +
    +u.name = "daisy"
    +assert u.name == "daisy"
    +
    +
    +
    +

    注解

    +

    Accessing column attributes on a model instance will NOT trigger any database +operation.

    +
    +

    Constraint and Index are +also allowed as model class attributes. Their attribute names are not used.

    +

    A concrete model class can be used as a replacement of the +Table it reflects in SQLAlchemy queries. The model class +is also iterable, yielding all the Column instances +defined in the model.

    +

    Other built-in class attributes:

    +
      +
    • __metadata__

      +

      This is supposed to be set by declarative_base() and used only during +subclass construction. Still, this can be treated as a read-only attribute to +find out which MetaData this model is bound to.

      +
    • +
    • __tablename__

      +

      This is a required attribute to define a concrete model, meaning a +sqlalchemy.schema.Table instance will be created, added to the bound +MetaData and set to the class attribute __table__. +Not defining __tablename__ will result in an abstract model - no table +instance will be created, and instances of an abstract model are meaningless.

      +
    • +
    • __table__

      +

      This should usually be treated as an auto-generated read-only attribute storing +the sqlalchemy.schema.Table instance.

      +
    • +
    • __attr_factory__

      +

      An attribute factory that is used to wrap the actual +Column instance on the model class, so that the access +to the column attributes on model instances is redirected to the in-memory value +store. The default factory is ColumnAttribute, can be override.

      +
    • +
    • __values__

      +

      The internal in-memory value store as a dict, only available on model +instances. Accessing column attributes is equivalent to accessing __values__.

      +
    • +
    +
    + +
    +
    +gino.declarative.declarative_base(metadata, model_classes=(<class 'gino.declarative.Model'>, ), name='Model')
    +

    Create a base GINO model class for declarative table definition.

    +
    +
    参数
    +
      +
    • metadata -- A MetaData instance to contain the +tables.

    • +
    • model_classes -- Base class(es) of the base model class to be created. Default: +Model.

    • +
    • name -- The class name of the base model class to be created. Default: +Model.

    • +
    +
    +
    返回
    +

    A new base model class.

    +
    +
    +
    + +
    +
    +gino.declarative.declared_attr(m=None, *, with_table=False)
    +

    Mark a class-level method as a factory of attribute.

    +

    This is intended to be used as decorators on class-level methods of a +Model class. When initializing the class as well as its +subclasses, the decorated factory method will be called for each class, the +returned result will be set on the class in place of the factory method +under the same name.

    +

    @declared_attr is implemented differently than +declared_attr of SQLAlchemy, but they +are both more often used on mixins to dynamically declare indices or +constraints (also works for column and __table_args__, or even normal +class attributes):

    +
    class TrackedMixin:
    +    created = db.Column(db.DateTime(timezone=True))
    +
    +    @db.declared_attr
    +    def unique_id(cls):
    +        return db.Column(db.Integer())
    +
    +    @db.declared_attr
    +    def unique_constraint(cls):
    +        return db.UniqueConstraint('unique_id')
    +
    +    @db.declared_attr
    +    def poly(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.Column(db.Unicode())
    +
    +    @db.declared_attr
    +    def __table_args__(cls):
    +        if cls.__name__ == 'Thing':
    +            return db.UniqueConstraint('poly'),
    +
    +
    +
    +

    注解

    +

    This doesn't work if the model already had a __table__.

    +
    +
    +

    在 1.1 版更改: Added with_table parameter which works after the __table__ is created:

    +
    class User(db.Model):
    +    __tablename__ = "users"
    +
    +    ...
    +
    +    @db.declared_attr(with_table=True)
    +    def table_name(cls):
    +        # this is called only once when defining the class
    +        return cls.__table__.name
    +
    +assert User.table_name == "users"
    +
    +
    +
    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.dialects.aiomysql.html b/docs/zh/master/reference/api/gino.dialects.aiomysql.html new file mode 100644 index 0000000..399adf8 --- /dev/null +++ b/docs/zh/master/reference/api/gino.dialects.aiomysql.html @@ -0,0 +1,594 @@ + + + + + + + + gino.dialects.aiomysql module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.aiomysql module

    +
    +
    +class gino.dialects.aiomysql.AiomysqlDBAPI
    +

    基类:gino.dialects.base.BaseDBAPI

    +
    +
    +paramstyle = 'format'
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlDialect(*args, bakery=None, **kwargs)
    +

    基类:sqlalchemy.dialects.mysql.base.MySQLDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.dialects.mysql.types._IntegerType'>: <class 'sqlalchemy.dialects.mysql.types._IntegerType'>, <class 'sqlalchemy.dialects.mysql.types._NumericType'>: <class 'sqlalchemy.dialects.mysql.types._NumericType'>, <class 'sqlalchemy.dialects.mysql.types._FloatType'>: <class 'sqlalchemy.dialects.mysql.types._FloatType'>, <class 'sqlalchemy.sql.sqltypes.Numeric'>: <class 'sqlalchemy.dialects.mysql.types.NUMERIC'>, <class 'sqlalchemy.sql.sqltypes.Float'>: <class 'sqlalchemy.dialects.mysql.types.FLOAT'>, <class 'sqlalchemy.sql.sqltypes.Time'>: <class 'sqlalchemy.dialects.mysql.types.TIME'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.MatchType'>: <class 'sqlalchemy.dialects.mysql.types._MatchType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.mysql.json.JSON'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONIndexType'>: <class 'sqlalchemy.dialects.mysql.json.JSONIndexType'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'sqlalchemy.dialects.mysql.json.JSONPathType'>, <class 'sqlalchemy.dialects.mysql.enumerated.ENUM'>: <class 'gino.dialects.aiomysql.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.aiomysql.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of gino.dialects.aiomysql.DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of gino.dialects.aiomysql.AiomysqlDBAPI

    +
    + +
    +
    +driver = 'aiomysql'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of gino.dialects.aiomysql.AiomysqlExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given a DBAPI connection, return its isolation level.

    +

    When working with a _engine.Connection object, +the corresponding +DBAPI connection may be procured using the +_engine.Connection.connection accessor.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine isolation level facilities; +these APIs should be preferred for most typical use cases.

    +
    +

    参见

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +init_kwargs = {'auth_plugin', 'autocommit', 'bakery', 'charset', 'client_flag', 'connect_timeout', 'conv', 'cursorclass', 'db', 'host', 'init_command', 'local_infile', 'loop', 'maxsize', 'minsize', 'no_delay', 'password', 'pool_recycle', 'port', 'prebake', 'program_name', 'read_default_file', 'read_default_group', 'server_public_key', 'sql_mode', 'ssl', 'unix_socket', 'use_unicode', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument "conn" which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The "do_on_connect" callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    返回
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    参见

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +postfetch_lastrowid = False
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given a DBAPI connection, set its isolation level.

    +

    Note that this is a dialect-level method which is used as part +of the implementation of the _engine.Connection and +_engine.Engine +isolation level facilities; these APIs should be preferred for +most typical use cases.

    +
    +

    参见

    +

    _engine.Connection.get_isolation_level() +- view current level

    +

    _engine.Connection.default_isolation_level +- view default level

    +

    :paramref:`.Connection.execution_options.isolation_level` - +set per _engine.Connection isolation level

    +

    :paramref:`_sa.create_engine.isolation_level` - +set per _engine.Engine isolation level

    +
    +
    + +
    +
    +statement_compiler
    +

    alias of sqlalchemy.dialects.mysql.base.MySQLCompiler

    +
    + +
    +
    +support_prepare = False
    +
    + +
    +
    +support_returning = False
    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlExecutionContext
    +

    基类:gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.mysql.base.MySQLExecutionContext

    +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +

    return self.cursor.lastrowid, or equivalent, after an INSERT.

    +

    This may involve calling special cursor functions, +issuing a new SELECT on the cursor (or a new one), +or returning a stored value that was +calculated within post_exec().

    +

    This function will only be called for dialects +which support "implicit" primary key generation, +keep preexecute_autoincrement_sequences set to False, +and when no explicit id value was bound to the +statement.

    +

    The function is called once, directly after +post_exec() and before the transaction is committed +or ResultProxy is generated. If the post_exec() +method assigns a value to self._lastrowid, the +value is used in place of calling get_lastrowid().

    +

    Note that this method is not equivalent to the +lastrowid method on ResultProxy, which is a +direct proxy to the DBAPI lastrowid accessor +in all cases.

    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AiomysqlIterator(context, cursor)
    +

    基类:gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.AsyncEnum(*enums, **kw)
    +

    基类:sqlalchemy.dialects.mysql.enumerated.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.DBAPICursor(dbapi_conn)
    +

    基类:gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +iterate(context)
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.GinoNullType
    +

    基类:sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +
      +
    • dialect -- Dialect instance in use.

    • +
    • coltype -- DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Pool(url, loop, init=None, bakery=None, prebake=True, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.aiomysql.Transaction(conn, set_isolation=None)
    +

    基类:gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.dialects.asyncpg.html b/docs/zh/master/reference/api/gino.dialects.asyncpg.html new file mode 100644 index 0000000..6078148 --- /dev/null +++ b/docs/zh/master/reference/api/gino.dialects.asyncpg.html @@ -0,0 +1,604 @@ + + + + + + + + gino.dialects.asyncpg module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.asyncpg module

    +
    +
    +class gino.dialects.asyncpg.AsyncEnum(*enums, **kw)
    +

    基类:sqlalchemy.dialects.postgresql.base.ENUM

    +
    +
    +async create_async(bind=None, checkfirst=True)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=True)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCompiler(dialect, statement, column_keys=None, inline=False, **kwargs)
    +

    基类:sqlalchemy.dialects.postgresql.base.PGCompiler

    +
    +
    +property bindtemplate
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgCursor(context, cursor)
    +

    基类:gino.dialects.base.Cursor

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDBAPI
    +

    基类:gino.dialects.base.BaseDBAPI

    +
    +
    +Error = (<class 'asyncpg.exceptions._base.PostgresError'>, <class 'asyncpg.exceptions._base.InterfaceError'>)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgDialect(*args, bakery=None, **kwargs)
    +

    基类:sqlalchemy.dialects.postgresql.base.PGDialect, gino.dialects.base.AsyncDialectMixin

    +
    +
    +colspecs = {<class 'sqlalchemy.sql.sqltypes.ARRAY'>: <class 'sqlalchemy.dialects.postgresql.array.ARRAY'>, <class 'sqlalchemy.sql.sqltypes.Interval'>: <class 'sqlalchemy.dialects.postgresql.base.INTERVAL'>, <class 'sqlalchemy.sql.sqltypes.Enum'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.JSON.JSONPathType'>: <class 'gino.dialects.asyncpg.AsyncpgJSONPathType'>, <class 'sqlalchemy.sql.sqltypes.JSON'>: <class 'sqlalchemy.dialects.postgresql.json.JSON'>, <class 'sqlalchemy.dialects.postgresql.base.ENUM'>: <class 'gino.dialects.asyncpg.AsyncEnum'>, <class 'sqlalchemy.sql.sqltypes.NullType'>: <class 'gino.dialects.asyncpg.GinoNullType'>}
    +
    + +
    +
    +cursor_cls
    +

    alias of gino.dialects.asyncpg.DBAPICursor

    +
    + +
    +
    +dbapi_class
    +

    alias of gino.dialects.asyncpg.AsyncpgDBAPI

    +
    + +
    +
    +driver = 'asyncpg'
    +
    + +
    +
    +execution_ctx_cls
    +

    alias of gino.dialects.asyncpg.AsyncpgExecutionContext

    +
    + +
    +
    +async get_isolation_level(connection)
    +

    Given an asyncpg connection, return its isolation level.

    +
    + +
    +
    +async has_schema(connection, schema)
    +
    + +
    +
    +async has_sequence(connection, sequence_name, schema=None)
    +

    Check the existence of a particular sequence in the database.

    +

    Given a _engine.Connection object and a string +sequence_name, return True if the given sequence exists in +the database, False otherwise.

    +
    + +
    +
    +async has_table(connection, table_name, schema=None)
    +

    Check the existence of a particular table in the database.

    +

    Given a _engine.Connection object and a string +table_name, return True if the given table (possibly within +the specified schema) exists in the database, False +otherwise.

    +
    + +
    +
    +async has_type(connection, type_name, schema=None)
    +
    + +
    +
    +init_kwargs = {'bakery', 'command_timeout', 'connection_class', 'database', 'host', 'init', 'loop', 'max_cacheable_statement_size', 'max_cached_statement_lifetime', 'max_inactive_connection_lifetime', 'max_queries', 'max_size', 'min_size', 'passfile', 'password', 'port', 'prebake', 'record_class', 'server_settings', 'setup', 'ssl', 'statement_cache_size', 'timeout', 'user'}
    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +on_connect()
    +

    Return a callable which sets up a newly created DBAPI connection.

    +

    The callable should accept a single argument "conn" which is the +DBAPI connection itself. The inner callable has no +return value.

    +

    E.g.:

    +
    class MyDialect(default.DefaultDialect):
    +    # ...
    +
    +    def on_connect(self):
    +        def do_on_connect(connection):
    +            connection.execute("SET SPECIAL FLAGS etc")
    +
    +        return do_on_connect
    +
    +
    +

    This is used to set dialect-wide per-connection options such as +isolation modes, Unicode modes, etc.

    +

    The "do_on_connect" callable is invoked by using the +_events.PoolEvents.first_connect() and +_events.PoolEvents.connect() event +hooks, then unwrapping the DBAPI connection and passing it into the +callable. The reason it is invoked for both events is so that any +dialect-level initialization that occurs upon first connection, which +also makes use of the _events.PoolEvents.first_connect() method, +will +proceed after this hook has been called. This currently means the +hook is in fact called twice for the very first connection in which a +dialect creates; and once per connection afterwards.

    +

    If None is returned, no event listener is generated.

    +
    +
    返回
    +

    a callable that accepts a single DBAPI connection as an +argument, or None.

    +
    +
    +
    +

    参见

    +

    Dialect.connect() - allows the DBAPI connect() sequence +itself to be controlled.

    +
    +
    + +
    +
    +async set_isolation_level(connection, level)
    +

    Given an asyncpg connection, set its isolation level.

    +
    + +
    +
    +statement_compiler
    +

    alias of gino.dialects.asyncpg.AsyncpgCompiler

    +
    + +
    +
    +supports_native_decimal = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgExecutionContext
    +

    基类:gino.dialects.base.ExecutionContextOverride, sqlalchemy.dialects.postgresql.base.PGExecutionContext

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgIterator(context, iterator)
    +

    基类:object

    +
    + +
    +
    +class gino.dialects.asyncpg.AsyncpgJSONPathType
    +

    基类:sqlalchemy.dialects.postgresql.json.JSONPathType

    +
    +
    +bind_processor(dialect)
    +

    Return a conversion function for processing bind values.

    +

    Returns a callable which will receive a bind parameter value +as the sole positional argument and will return a value to +send to the DB-API.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +

    dialect -- Dialect instance in use.

    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.DBAPICursor(dbapi_conn)
    +

    基类:gino.dialects.base.DBAPICursor

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.GinoNullType
    +

    基类:sqlalchemy.sql.sqltypes.NullType

    +
    +
    +result_processor(dialect, coltype)
    +

    Return a conversion function for processing result row values.

    +

    Returns a callable which will receive a result row column +value as the sole positional argument and will return a value +to return to the user.

    +

    If processing is not necessary, the method should return None.

    +
    +
    参数
    +
      +
    • dialect -- Dialect instance in use.

    • +
    • coltype -- DBAPI coltype argument received in cursor.description.

    • +
    +
    +
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.NullPool(url, loop, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.Pool(url, loop, bakery=None, prebake=True, **kwargs)
    +

    基类:gino.dialects.base.Pool

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.asyncpg.PreparedStatement(prepared, clause=None)
    +

    基类:gino.dialects.base.PreparedStatement

    +
    + +
    +
    +class gino.dialects.asyncpg.Transaction(tx)
    +

    基类:gino.dialects.base.Transaction

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.dialects.base.html b/docs/zh/master/reference/api/gino.dialects.base.html new file mode 100644 index 0000000..395fd31 --- /dev/null +++ b/docs/zh/master/reference/api/gino.dialects.base.html @@ -0,0 +1,488 @@ + + + + + + + + gino.dialects.base module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects.base module

    +
    +
    +class gino.dialects.base.AsyncDialectMixin
    +

    基类:object

    +
    +
    +compile(elem, *multiparams, **params)
    +
    + +
    +
    +cursor_cls
    +

    alias of gino.dialects.base.DBAPICursor

    +
    + +
    +
    +classmethod dbapi()
    +
    + +
    +
    +dbapi_class
    +

    alias of gino.dialects.base.BaseDBAPI

    +
    + +
    +
    +async init_pool(url, loop, pool_class=None)
    +
    + +
    +
    +support_prepare = True
    +
    + +
    +
    +support_returning = True
    +
    + +
    +
    +transaction(raw_conn, args, kwargs)
    +
    + +
    + +
    +
    +class gino.dialects.base.BaseDBAPI
    +

    基类:object

    +
    +
    +static Binary(x)
    +
    + +
    +
    +Error
    +

    alias of Exception

    +
    + +
    +
    +paramstyle = 'numeric'
    +
    + +
    + +
    +
    +class gino.dialects.base.Cursor
    +

    基类:object

    +
    +
    +async forward(n, *, timeout=<object object>)
    +
    + +
    +
    +async many(n, *, timeout=<object object>)
    +
    + +
    +
    +async next(*, timeout=<object object>)
    +
    + +
    + +
    +
    +class gino.dialects.base.DBAPICursor
    +

    基类:object

    +
    +
    +async async_execute(query, timeout, args, limit=0, many=False)
    +
    + +
    +
    +property description
    +
    + +
    +
    +execute(statement, parameters)
    +
    + +
    +
    +async execute_baked(baked_query, timeout, args, one)
    +
    + +
    +
    +executemany(statement, parameters)
    +
    + +
    +
    +get_statusmsg()
    +
    + +
    +
    +async prepare(context, clause=None)
    +
    + +
    + +
    +
    +class gino.dialects.base.ExecutionContextOverride
    +

    基类:object

    +
    +
    +baked_query = None
    +
    + +
    +
    +get_affected_rows()
    +
    + +
    +
    +get_lastrowid()
    +
    + +
    +
    +get_result_proxy()
    +
    + +
    +
    +loader
    +
    + +
    +
    +model
    +
    + +
    +
    +process_rows(rows, return_model=True)
    +
    + +
    +
    +return_model
    +
    + +
    +
    +timeout
    +
    + +
    + +
    +
    +class gino.dialects.base.Pool
    +

    基类:object

    +
    +
    +async acquire(*, timeout=None)
    +
    + +
    +
    +async close()
    +
    + +
    +
    +property raw_pool
    +
    + +
    +
    +async release(conn)
    +
    + +
    +
    +repr(color)
    +
    + +
    + +
    +
    +class gino.dialects.base.PreparedStatement(clause=None)
    +

    基类:object

    +
    +
    +async all(*multiparams, **params)
    +
    + +
    +
    +async first(*multiparams, **params)
    +
    + +
    +
    +iterate(*params, **kwargs)
    +
    + +
    +
    +async scalar(*multiparams, **params)
    +
    + +
    +
    +async status(*multiparams, **params)
    +
    + +
    + +
    +
    +class gino.dialects.base.Transaction
    +

    基类:object

    +
    +
    +async begin()
    +
    + +
    +
    +async commit()
    +
    + +
    +
    +property raw_transaction
    +
    + +
    +
    +async rollback()
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.dialects.html b/docs/zh/master/reference/api/gino.dialects.html new file mode 100644 index 0000000..5ee55bd --- /dev/null +++ b/docs/zh/master/reference/api/gino.dialects.html @@ -0,0 +1,233 @@ + + + + + + + + gino.dialects package - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.dialects package

    +
    +

    Submodules

    + +
    +
    +

    Module contents

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.engine.html b/docs/zh/master/reference/api/gino.engine.html new file mode 100644 index 0000000..60ca996 --- /dev/null +++ b/docs/zh/master/reference/api/gino.engine.html @@ -0,0 +1,724 @@ + + + + + + + + gino.engine module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.engine module

    +
    +
    +class gino.engine.GinoConnection(dialect, sa_conn, stack=None)
    +

    基类:object

    +

    Represents an actual database connection.

    +

    This is the root of all query API like all(), first(), +one(), one_or_none(), scalar() or status(), +those on engine or query are simply wrappers of methods in this class.

    +

    Usually instances of this class are created by GinoEngine.acquire().

    +
    +

    注解

    +

    GinoConnection may refer to zero or one underlying database +connection - when a GinoConnection is acquired with +lazy=True, the underlying connection may still be in the pool, +until a query API is called or get_raw_connection() is called.

    +

    Oppositely, one underlying database connection can be shared by many +GinoConnection instances when they are acquired with +reuse=True. The actual database connection is only returned to the +pool when the root GinoConnection is released. Read more +in GinoEngine.acquire() method.

    +
    +
    +

    参见

    +

    引擎与连接

    +
    +
    +
    +async all(clause, *multiparams, **params)
    +

    Runs the given query in database, returns all results as a list.

    +

    This method accepts the same parameters taken by SQLAlchemy +execute(). You can pass in a raw +SQL string, or any SQLAlchemy query clauses.

    +

    If the given query clause is built by CRUD models, then the returning +rows will be turned into relevant model objects (Only one type of model +per query is supported for now, no relationship support yet). See +execution_options() for more information.

    +

    If the given parameters are parsed as "executemany" - bulk inserting +multiple rows in one call for example, the returning result from +database will be discarded and this method will return None.

    +
    + +
    +
    +property dialect
    +

    The Dialect in use, inherited +from the engine created this connection.

    +
    + +
    +
    +execution_options(**opt)
    +

    Set non-SQL options for the connection which take effect during +execution.

    +

    This method returns a copy of this GinoConnection which +references the same underlying database connection, but with the given +execution options set on the copy. Therefore, it is a good practice to +discard the copy immediately after use, for example:

    +
    row = await conn.execution_options(model=None).first(User.query)
    +
    +
    +

    This is very much the same as SQLAlchemy +execution_options(), it +actually does pass the execution options to the underlying SQLAlchemy +Connection. Furthermore, GINO added a +few execution options:

    +
    +
    参数
    +
      +
    • return_model -- Boolean to control whether the returning results +should be loaded into model instances, where the model class is +defined in another execution option model. Default is True.

    • +
    • model -- Specifies the type of model instance to create on return. +This has no effect if return_model is set to False. Usually +in queries built by CRUD models, this execution option is +automatically set. For now, GINO only supports loading each row into +one type of model object, relationships are not supported. Please use +multiple queries for that. None for no postprocessing (default).

    • +
    • timeout -- Seconds to wait for the query to finish. None for +no time out (default).

    • +
    • loader --

      A loader expression to load the database rows into +specified objective structure. It can be either:

      +
        +
      • A model class, so that the query will yield model instances of this +class. It is your responsibility to make sure all the columns of +this model is selected in the query.

      • +
      • A Column instance, so that each result +will be only a single value of this column. Please note, if you +want to achieve fetching the very first value, you should use +first() instead of +scalar(). However, using directly +scalar() is a more direct way.

      • +
      • A tuple nesting more loader expressions recursively.

      • +
      • A callable() function that will be called for each row to +fully customize the result. Two positional arguments will be passed +to the function: the first is the row instance, the second is a context +object which is only present if nested else None.

      • +
      • A Loader instance directly.

      • +
      • Anything else will be treated as literal values thus returned as +whatever they are.

      • +
      +

    • +
    +
    +
    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +async get_raw_connection(*, timeout=None)
    +

    Get the underlying database connection, acquire one if none present.

    +
    +
    参数
    +

    timeout -- Seconds to wait for the underlying acquiring

    +
    +
    返回
    +

    Underlying database connection instance depending on the +dialect in use

    +
    +
    Raises
    +

    TimeoutError if the acquiring timed out

    +
    +
    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    Cursors must work within transactions:

    +
    async with conn.transaction():
    +    async for user in conn.iterate(User.query):
    +        # handle each user without loading all users into memory
    +
    +
    +

    Alternatively, you can manually control how the cursor works:

    +
    async with conn.transaction():
    +    cursor = await conn.iterate(User.query)
    +    user = await cursor.next()
    +    users = await cursor.many(10)
    +
    +
    +

    Read more about how Cursor works.

    +

    Similarly, this method takes the same parameters as all().

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs the given query in database, returns exactly one result.

    +

    If the query returns no result, this method will raise +NoResultFound. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs the given query in database, returns at most one result.

    +

    If the query returns no result, this method will return None. +If the query returns multiple results, this method will raise +MultipleResultsFound.

    +

    See all() for common query comments.

    +
    + +
    +
    +async prepare(clause)
    +
    + +
    +
    +property raw_connection
    +

    The current underlying database connection instance, type depends on +the dialect in use. May be None if self is a lazy connection.

    +
    + +
    +
    +async release(*, permanent=True)
    +

    Returns the underlying database connection to its pool.

    +

    If permanent=False, this connection will be set in lazy mode with +underlying database connection returned, the next query on this +connection will cause a new database connection acquired. This is +useful when this connection may still be useful again later, while some +long-running I/O operations are about to take place, which should not +take up one database connection or even transaction for that long time.

    +

    Otherwise with permanent=True (default), this connection will be +marked as closed after returning to pool, and be no longer usable +again.

    +

    If this connection is a reusing connection, then only this connection +is closed (depending on permanent), the reused underlying +connection will not be returned back to the pool.

    +

    Practically it is recommended to return connections in the reversed +order as they are borrowed, but if this connection is a reused +connection with still other opening connections reusing it, then on +release the underlying connection will be returned to the pool, +with all the reusing connections losing an available underlying +connection. The availability of further operations on those reusing +connections depends on the given permanent value.

    + +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the first result.

    +

    If the query returns no result, this method will return None.

    +

    See all() for common query comments.

    +
    + +
    +
    +schema_for_object = <sqlalchemy.sql.schema._SchemaTranslateMap object>
    +

    A SQLAlchemy compatibility attribute, don't use it for now, it bites.

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs the given query in database, returns the query status.

    +

    The returning query status depends on underlying database and the +dialect in use. For asyncpg it is a string, you can parse it like this: +https://git.io/v7oze

    +
    + +
    +
    +transaction(*args, **kwargs)
    +

    Starts a database transaction.

    +

    There are two ways using this method: managed as an asynchronous +context manager:

    +
    async with conn.transaction() as tx:
    +    # run query in transaction
    +
    +
    +

    or manually awaited:

    +
    tx = await conn.transaction()
    +try:
    +    # run query in transaction
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    Where the tx is an instance of the +GinoTransaction class, feel free to read +more about it.

    +

    In the first managed mode, the transaction is automatically committed +on exiting the context block, or rolled back if an exception was raised +which led to the exit of the context. In the second manual mode, you'll +need to manually call the +commit() or +rollback() methods on need.

    +

    If this is a lazy connection, entering a transaction will cause a new +database connection acquired if none was present.

    +

    Transactions may support nesting depending on the dialect in use. For +example in asyncpg, starting a second transaction on the same +connection will create a save point in the database.

    +

    For now, the parameters are directly passed to underlying database +driver, read asyncpg.connection.Connection.transaction() for +asyncpg.

    +
    + +
    + +
    +
    +class gino.engine.GinoEngine(dialect, pool, loop, logging_name=None, echo=None, execution_options=None)
    +

    基类:object

    +

    Connects a Pool and +Dialect together to provide a source +of database connectivity and behavior.

    +

    A GinoEngine object is instantiated publicly using the +gino.create_engine() function or +db.set_bind() method.

    +
    +

    参见

    +

    引擎与连接

    +
    +
    +
    +acquire(*, timeout=None, reuse=False, lazy=False, reusable=True)
    +

    Acquire a connection from the pool.

    +

    There are two ways using this method - as an asynchronous context +manager:

    +
    async with engine.acquire() as conn:
    +    # play with the connection
    +
    +
    +

    which will guarantee the connection is returned to the pool when +leaving the async with block; or as a coroutine:

    +
    conn = await engine.acquire()
    +try:
    +    # play with the connection
    +finally:
    +    await conn.release()
    +
    +
    +

    where the connection should be manually returned to the pool with +conn.release().

    +

    Within the same context (usually the same Task, see +also 数据库事务), a nesting acquire by default re

    +
    +
    参数
    +
      +
    • timeout -- Block up to timeout seconds until there is one free +connection in the pool. Default is None - block forever until +succeeded. This has no effect when lazy=True, and depends on the +actual situation when reuse=True.

    • +
    • reuse -- Reuse the latest reusable acquired connection (before +it's returned to the pool) in current context if there is one, or +borrow a new one if none present. Default is False for always +borrow a new one. This is useful when you are in a nested method call +series, wishing to use the same connection without passing it around +as parameters. See also: 数据库事务. A reusing +connection is not reusable even if reusable=True. If the reused +connection happened to be a lazy one, then the reusing connection is +lazy too.

    • +
    • lazy -- Don't acquire the actual underlying connection yet - do it +only when needed. Default is False for always do it immediately. +This is useful before entering a code block which may or may not make +use of a given connection object. Feeding in a lazy connection will +save the borrow-return job if the connection is never used. If +setting reuse=True at the same time, then the reused connection - +if any - applies the same laziness. For example, reusing a lazy +connection with lazy=False will cause the reused connection to +acquire an underlying connection immediately.

    • +
    • reusable -- Mark this connection as reusable or otherwise. This +has no effect if it is a reusing connection. All reusable connections +are placed in a stack, any reusing acquire operation will always +reuse the top (latest) reusable connection. One reusable connection +may be reused by several reusing connections - they all share one +same underlying connection. Acquiring a connection with +reusable=False and reusing=False makes it a cleanly isolated +connection which is only referenced once here.

    • +
    +
    +
    返回
    +

    A GinoConnection object.

    +
    +
    +
    + +
    +
    +async all(clause, *multiparams, **params)
    +

    Acquires a connection with reuse=True and runs +all() on it. reuse=True means you can safely +do this without borrowing more than one underlying connection:

    +
    async with engine.acquire():
    +    await engine.all('SELECT ...')
    +
    +
    +

    The same applies for other query methods.

    +
    + +
    +
    +async close()
    +

    Close the engine, by closing the underlying pool.

    +
    + +
    +
    +compile(clause, *multiparams, **params)
    +

    A shortcut for compile() on +the dialect, returns raw SQL string and parameters according to the +rules of the dialect.

    +
    + +
    +
    +connection_cls
    +

    Customizes the connection class to use, default is +GinoConnection.

    +

    alias of gino.engine.GinoConnection

    +
    + +
    +
    +property current_connection
    +

    Gets the most recently acquired reusable connection in the context. +None if there is no such connection.

    +
    +
    返回
    +

    GinoConnection

    +
    +
    +
    + +
    +
    +property dialect
    +

    Read-only property for the +Dialect of this engine.

    +
    + +
    +
    +async first(clause, *multiparams, **params)
    +

    Runs first(), See all().

    +
    + +
    +
    +iterate(clause, *multiparams, **params)
    +

    Creates a server-side cursor in database for large query results.

    +

    This requires that there is a reusable connection in the current +context, and an active transaction is present. Then its +GinoConnection.iterate() is executed and returned.

    +
    + +
    +
    +async one(clause, *multiparams, **params)
    +

    Runs one(), See all().

    +
    + +
    +
    +async one_or_none(clause, *multiparams, **params)
    +

    Runs one_or_none(), See all().

    +
    + +
    +
    +property raw_pool
    +

    Read-only access to the underlying database connection pool instance. +This depends on the actual dialect in use, Pool +of asyncpg for example.

    +
    + +
    +
    +repr(color=False)
    +
    + +
    +
    +async scalar(clause, *multiparams, **params)
    +

    Runs scalar(), See all().

    +
    + +
    +
    +async status(clause, *multiparams, **params)
    +

    Runs status(). See also all().

    +
    + +
    +
    +transaction(*args, timeout=None, reuse=True, reusable=True, **kwargs)
    +

    Borrows a new connection and starts a transaction with it.

    +

    Different to GinoConnection.transaction(), transaction on engine +level supports only managed usage:

    +
    async with engine.transaction() as tx:
    +    # play with transaction here
    +
    +
    +

    Where the implicitly acquired connection is available as +tx.connection.

    +

    By default, transaction() acquires connection with +reuse=True and reusable=True, that means it by default tries to +create a nested transaction instead of a new transaction on a new +connection. You can change the default behavior by setting these two +arguments.

    +

    The other arguments are the same as +transaction() on connection.

    + +
    +
    返回
    +

    A asynchronous context manager that yields a +GinoTransaction

    +
    +
    +
    + +
    +
    +update_execution_options(**opt)
    +

    Update the default execution_options dictionary +of this GinoEngine.

    + +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.exceptions.html b/docs/zh/master/reference/api/gino.exceptions.html new file mode 100644 index 0000000..be1a581 --- /dev/null +++ b/docs/zh/master/reference/api/gino.exceptions.html @@ -0,0 +1,256 @@ + + + + + + + + gino.exceptions module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.exceptions module

    +
    +
    +exception gino.exceptions.GinoException
    +

    基类:Exception

    +
    + +
    +
    +exception gino.exceptions.InitializedError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.MultipleResultsFound
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoResultFound
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.NoSuchRowError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UninitializedError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    +
    +exception gino.exceptions.UnknownJSONPropertyError
    +

    基类:gino.exceptions.GinoException

    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.ext.html b/docs/zh/master/reference/api/gino.ext.html new file mode 100644 index 0000000..cba950b --- /dev/null +++ b/docs/zh/master/reference/api/gino.ext.html @@ -0,0 +1,233 @@ + + + + + + + + gino.ext package - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.ext package

    +
    +

    Module contents

    +

    Namespace package for GINO extensions.

    +

    This namespace package didn't use any of the 3 official solutions. Instead, +gino.ext adds a hook into sys.meta_path, and utilize the Entry points +to find the extensions.

    +

    Any GINO extension package should provide an entry point like this:

    +
    [gino.extensions]
    +starlette = gino_starlette
    +
    +
    +

    So that the Python package gino_starlette will also be importable through +gino.ext.starlette.

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.html b/docs/zh/master/reference/api/gino.html new file mode 100644 index 0000000..08b8f70 --- /dev/null +++ b/docs/zh/master/reference/api/gino.html @@ -0,0 +1,286 @@ + + + + + + + + gino package - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino package

    +
    +

    Subpackages

    + +
    +
    +

    Submodules

    + +
    +
    +

    Module contents

    +
    +
    +gino.create_engine(*args, **kwargs)
    +

    Shortcut for sqlalchemy.create_engine() with strategy="gino".

    +
    +

    在 1.1 版更改: Added the bakery keyword argument, please see Bakery.

    +
    +
    +

    在 1.1 版更改: Added the prebake keyword argument to choose when to create the prepared +statements for the queries in the bakery:

    +
      +
    • Pre-bake immediately when connected to the database (default).

    • +
    • No pre-bake but create prepared statements lazily when needed for the first +time.

    • +
    +

    Note: prebake has no effect in aiomysql

    +
    +
    + +
    +
    +gino.get_version()
    +

    Get current GINO version.

    +
    + +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.json_support.html b/docs/zh/master/reference/api/gino.json_support.html new file mode 100644 index 0000000..03a7f10 --- /dev/null +++ b/docs/zh/master/reference/api/gino.json_support.html @@ -0,0 +1,356 @@ + + + + + + + + gino.json_support module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.json_support module

    +
    +
    +class gino.json_support.ArrayProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.BooleanProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.DateTimeProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.IntegerProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    +
    +class gino.json_support.JSONProperty(default=None, prop_name='profile')
    +

    基类:object

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    +
    +get_profile(instance)
    +
    + +
    +
    +make_expression(base_exp)
    +
    + +
    +
    +reload(instance)
    +
    + +
    +
    +save(instance, value=<object object>)
    +
    + +
    + +
    +
    +class gino.json_support.ObjectProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +decode(val)
    +
    + +
    +
    +encode(val)
    +
    + +
    + +
    +
    +class gino.json_support.StringProperty(default=None, prop_name='profile')
    +

    基类:gino.json_support.JSONProperty

    +
    +
    +make_expression(base_exp)
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.loader.html b/docs/zh/master/reference/api/gino.loader.html new file mode 100644 index 0000000..18017a5 --- /dev/null +++ b/docs/zh/master/reference/api/gino.loader.html @@ -0,0 +1,644 @@ + + + + + + + + gino.loader module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.loader module

    +
    +
    +class gino.loader.AliasLoader(alias, *columns, **extras)
    +

    基类:gino.loader.ModelLoader

    +

    The same as ModelLoader, kept for compatibility.

    +
    + +
    +
    +class gino.loader.CallableLoader(func)
    +

    基类:gino.loader.Loader

    +

    Load the row by calling a specified function.

    +
    +
    参数
    +

    func -- A callable() taking 2 positional arguments (row, context) +that will be called in do_load(), returning the loaded result.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to the given function.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    The result calling the given function, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ColumnLoader(column)
    +

    基类:gino.loader.Loader

    +

    Load a given column in the row.

    +
    +
    参数
    +

    column -- The column name as str, or a +Column instance to avoid name conflict.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- Not used.

    • +
    +
    +
    返回
    +

    The value of the specified column, followed by True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.Loader
    +

    基类:object

    +

    The abstract base class of loaders.

    +

    Loaders are used to load raw database rows into expected results. +GinoEngine will use the loader set on the loader value of +the execution_options(), for example:

    +
    from sqlalchemy import text, create_engine
    +from gino.loader import ColumnLoader
    +
    +e = await create_engine("postgresql://localhost/gino", strategy="gino")
    +q = text("SELECT now() as ts")
    +loader = ColumnLoader("ts")
    +ts = await e.first(q.execution_options(loader=loader))  # datetime
    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    Must be implemented in subclasses.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    Any result that the loader is supposed to return, followed by a boolean +value indicating if the result is distinct.

    +
    +
    +
    + +
    +
    +classmethod get(value)
    +

    Automatically create a loader based on the type of the given value.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    value type

    loader type

    tuple

    TupleLoader

    callable()

    CallableLoader

    Column, +Label

    ColumnLoader

    Model

    ModelLoader

    Alias

    AliasLoader

    Loader

    as is

    any other types

    ValueLoader

    +
    +
    参数
    +

    value -- Any supported value above.

    +
    +
    返回
    +

    A loader instance.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +property query
    +

    Generate a query from this loader.

    +

    This is an experimental feature, not all loaders support this.

    +
    +
    返回
    +

    A query instance with the loader execution option set to self.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ModelLoader(model, *columns, **extras)
    +

    基类:gino.loader.Loader

    +

    A loader that loads a row into a GINO model instance.

    +

    This loader generates an instance of the given model type and fills the instance +with attributes according to the other given parameters:

    +
      +
    • Load each column attribute listed in the given columns positional arguments.

    • +
    • If columns is not given, all defined columns of the model will be loaded.

    • +
    • For each keyword argument, its value will be used to generate a loader using +Loader.get(), and the loaded value will be setattr() to the model +instance under the name of the key.

    • +
    +

    For example, the simplest select and load:

    +
    sqlalchemy.select([User]).execution_options(loader=ModelLoader(User))
    +
    +
    +

    Select only the name column, and still load it into model instances:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, User.name)
    +)
    +
    +
    +

    This would also yield User instances, with all column attributes as None but +name.

    +

    Nest a ValueLoader:

    +
    sqlalchemy.select(
    +    [User.name]
    +).execution_options(
    +    loader=ModelLoader(User, id=1)
    +)
    +
    +
    +

    1 is then converted into a ValueLoader and mocked the id attribute +of all returned User instances as 1.

    +

    Nest another ModelLoader:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company)
    +)
    +
    +
    +

    Likewise, Company is converted into a ModelLoader to load the +Company columns from the joined result, and the Company instances are set to +the company attribute of each User instance using setattr().

    +
    +
    参数
    +
      +
    • model -- A subclass of Model to instantiate.

    • +
    • columns -- A list of Column or str to +load, default is all the columns in the model.

    • +
    • extras -- Additional attributes to load on the model instance.

    • +
    +
    +
    +
    +
    +property columns
    +
    + +
    +
    +distinct(*columns)
    +

    Configure this loader to reuse instances that have the same values of all the +give columns.

    +
    +
    参数
    +

    columns -- Preferably Column instances.

    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    The model instance, followed by a boolean value indicating if the +result is distinct.

    +
    +
    +
    + +
    +
    +get_columns()
    +

    Generate a list of selectables from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A list of SQLAlchemy selectables.

    +
    +
    +
    + +
    +
    +get_from()
    +

    Generate a clause to be used in +select_from() from this loader.

    +

    This is an experimental feature, this method is supposed to be called by +query.

    +
    +
    返回
    +

    A FromClause instance, or None.

    +
    +
    +
    + +
    +
    +load(*columns, **extras)
    +

    Update the loader with new rules.

    +

    After initialization, the rules of this loader can still be updated. This is +useful when using the model class as a shortcut of ModelLoader where +possible, chaining with a load() to initialize the rules, for example:

    +
    sqlalchemy.select(
    +    [user.outerjoin(Company)]
    +).execution_options(
    +    loader=ModelLoader(User, company=Company.load('name'))
    +)
    +
    +
    +
    +
    参数
    +
      +
    • columns -- If provided, replace the columns to load with the given ones.

    • +
    • extras -- Update the loader with new extras.

    • +
    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    +
    +none_as_none(enabled=True)
    +

    Deprecated method for compatibility, does nothing.

    +
    + +
    +
    +on(on_clause)
    +

    Specify the on_clause for generating joined queries.

    +

    This is an experimental feature, used by get_from().

    +
    +
    参数
    +

    on_clause -- An expression to feed into +join().

    +
    +
    返回
    +

    self for chaining.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.TupleLoader(values)
    +

    基类:gino.loader.Loader

    +

    Load multiple values into a tuple.

    +
    +
    参数
    +

    values -- A tuple, each item is converted into a loader with +Loader.get().

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +

    The arguments are simply passed to sub-loaders.

    +
    +
    参数
    +
      +
    • row -- A RowProxy instance.

    • +
    • context -- A dict that is reused across all loaders in one query.

    • +
    +
    +
    返回
    +

    A tuple with loaded results from all sub-loaders, followed by +True.

    +
    +
    +
    + +
    + +
    +
    +class gino.loader.ValueLoader(value)
    +

    基类:gino.loader.Loader

    +

    A loader that always return the specified value.

    +
    +
    参数
    +

    value -- The value to return on load.

    +
    +
    +
    +
    +do_load(row, context)
    +

    Interface used by GINO to run the loader.

    +
    +
    参数
    +
      +
    • row -- Not used.

    • +
    • context -- Not used.

    • +
    +
    +
    返回
    +

    The given value, followed by True.

    +
    +
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.schema.html b/docs/zh/master/reference/api/gino.schema.html new file mode 100644 index 0000000..c9e87df --- /dev/null +++ b/docs/zh/master/reference/api/gino.schema.html @@ -0,0 +1,334 @@ + + + + + + + + gino.schema module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.schema module

    +
    +
    +class gino.schema.AsyncSchemaDropper(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    基类:gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaDropper

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, drop_ok=False)
    +
    + +
    +
    +async visit_table(table, drop_ok=False, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaGenerator(dialect, connection, checkfirst=False, tables=None, **kwargs)
    +

    基类:gino.schema.AsyncVisitor, sqlalchemy.sql.ddl.SchemaGenerator

    +
    +
    +async visit_foreign_key_constraint(constraint)
    +
    + +
    +
    +async visit_index(index)
    +
    + +
    +
    +async visit_metadata(metadata)
    +
    + +
    +
    +async visit_sequence(sequence, create_ok=False)
    +
    + +
    +
    +async visit_table(table, create_ok=False, include_foreign_key_constraints=None, _is_metadata_operation=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncSchemaTypeMixin
    +

    基类:object

    +
    +
    +async create_async(bind=None, checkfirst=False)
    +
    + +
    +
    +async drop_async(bind=None, checkfirst=False)
    +
    + +
    + +
    +
    +class gino.schema.AsyncVisitor
    +

    基类:object

    +
    +
    +async traverse_single(obj, **kw)
    +
    + +
    + +
    +
    +class gino.schema.GinoSchemaVisitor(item)
    +

    基类:object

    +
    +
    +async create(bind=None, *args, **kwargs)
    +
    + +
    +
    +async create_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    +
    +async drop(bind=None, *args, **kwargs)
    +
    + +
    +
    +async drop_all(bind=None, tables=None, checkfirst=True)
    +
    + +
    + +
    +
    +gino.schema.patch_schema(db)
    +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.strategies.html b/docs/zh/master/reference/api/gino.strategies.html new file mode 100644 index 0000000..e7300e9 --- /dev/null +++ b/docs/zh/master/reference/api/gino.strategies.html @@ -0,0 +1,242 @@ + + + + + + + + gino.strategies module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.strategies module

    +
    +
    +class gino.strategies.GinoStrategy
    +

    基类:sqlalchemy.engine.strategies.EngineStrategy

    +

    A SQLAlchemy engine strategy for GINO.

    +

    This strategy is initialized automatically as gino is imported.

    +

    If sqlalchemy.create_engine() uses strategy="gino", it will return a +Coroutine, and treat URL prefix postgresql:// or +postgres:// as postgresql+asyncpg://.

    +
    +
    +async create(name_or_url, loop=None, **kwargs)
    +

    Given arguments, returns a new Engine instance.

    +
    + +
    +
    +engine_cls
    +

    alias of gino.engine.GinoEngine

    +
    + +
    +
    +name = 'gino'
    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/api/gino.transaction.html b/docs/zh/master/reference/api/gino.transaction.html new file mode 100644 index 0000000..118fdd0 --- /dev/null +++ b/docs/zh/master/reference/api/gino.transaction.html @@ -0,0 +1,338 @@ + + + + + + + + gino.transaction module - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    gino.transaction module

    +
    +
    +class gino.transaction.GinoTransaction(conn, args, kwargs)
    +

    基类:object

    +

    Represents an underlying database transaction and its connection, offering +methods to manage this transaction.

    +

    GinoTransaction is supposed to be created by either +gino.engine.GinoConnection.transaction(), or +gino.engine.GinoEngine.transaction(), or +gino.api.Gino.transaction(), shown as follows:

    +
    async with db.transaction() as tx:
    +    ...
    +
    +async with engine.transaction() as tx:
    +    ...
    +
    +async with conn.transaction() as tx:
    +    ...
    +
    +tx = await conn.transaction()
    +try:
    +    ...
    +    await tx.commit()
    +except Exception:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    When in use with asynchronous context manager, GinoTransaction +will be in managed mode, while the last example with await will put +the GinoTransaction in manual mode where you have to call +the commit() or rollback() to manually close the transaction.

    +

    In managed mode the transaction will be automatically committed or +rolled back on exiting the async with block depending on whether there +is an exception or not. Meanwhile, you can explicitly exit the transaction +early by raise_commit() or raise_rollback() which will raise +an internal exception managed by the asynchronous context manager and +interpreted as a commit or rollback action. In a nested transaction +situation, the two exit-early methods always close up the very transaction +which the two methods are referenced upon - all children transactions are +either committed or rolled back correspondingly, while no parent +transaction was ever touched. For example:

    +
    async with db.transaction() as tx1:
    +    async with db.transaction() as tx2:
    +        async with db.transaction() as tx3:
    +            tx2.raise_rollback()
    +            # Won't reach here
    +        # Won't reach here
    +    # Continues here with tx1, with both tx2 and tx3 rolled back.
    +
    +    # For PostgreSQL, tx1 can still be committed successfully because
    +    # tx2 and tx3 are just SAVEPOINTs in transaction tx1
    +
    +
    +
    +

    小技巧

    +

    The internal exception raised from raise_commit() and +raise_rollback() is a subclass of BaseException, so +normal try ... except Exception: can't trap the commit or rollback.

    +
    +
    +
    +async commit()
    +

    Only available in manual mode: manually commit this transaction.

    +
    + +
    +
    +property connection
    +

    Accesses to the GinoConnection of this +transaction. This is useful if when the transaction is started from +db or engine where the connection is implicitly acquired for +you together with the transaction.

    +
    + +
    +
    +raise_commit()
    +

    Only available in managed mode: skip rest of the code in this +transaction and commit immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    async with db.transaction() as tx:
    +    await user.update(age=64).apply()
    +    tx.raise_commit()
    +    await user.update(age=32).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +raise_rollback()
    +

    Only available in managed mode: skip rest of the code in this +transaction and rollback immediately by raising an internal exception, +which will be caught and handled by the asynchronous context manager:

    +
    assert user.age == 64  # assumption
    +
    +async with db.transaction() as tx:
    +    await user.update(age=32).apply()
    +    tx.raise_rollback()
    +    await user.update(age=128).apply()  # won't reach here
    +
    +assert user.age == 64  # no exception raised before
    +
    +
    +
    + +
    +
    +property raw_transaction
    +

    Accesses to the underlying transaction object, whose type depends on +the dialect in use.

    +
    + +
    +
    +async rollback()
    +

    Only available in manual mode: manually rollback this transaction.

    +
    + +
    + +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/authors.html b/docs/zh/master/reference/authors.html new file mode 100644 index 0000000..d6dc800 --- /dev/null +++ b/docs/zh/master/reference/authors.html @@ -0,0 +1,221 @@ + + + + + + + + 制作人员 - GINO 1.0.0rc3 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    制作人员

    +
    +

    原作者

    + +
    +
    +

    维护者

    + +
    +
    +

    贡献者

    + +

    Special thanks to my wife Daisy and her outsourcing company DecentFoX Studio, +for offering me the opportunity to build this project. We are open for global +software project outsourcing on Python, iOS and Android development.

    +
    +
    + + +
    +
    + +
    + +
    + +
    +
    + + +
    + ZH +
    +
    + +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/extensions.html b/docs/zh/master/reference/extensions.html new file mode 100644 index 0000000..15fc994 --- /dev/null +++ b/docs/zh/master/reference/extensions.html @@ -0,0 +1,226 @@ + + + + + + + + 扩展 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    + + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/extensions/sanic.html b/docs/zh/master/reference/extensions/sanic.html new file mode 100644 index 0000000..8177cb3 --- /dev/null +++ b/docs/zh/master/reference/extensions/sanic.html @@ -0,0 +1,337 @@ + + + + + + + + Sanic Support - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Sanic Support

    +

    THIS IS A WIP

    +
    +

    Work with Sanic

    +

    Using the Sanic extension, the request handler acquires a lazy connection on each request, +and return the connection when the response finishes by default.

    +

    The lazy connection is actually established if necessary, i.e. just before first access to db.

    +

    This behavior is controlled by app.config.DB_USE_CONNECTION_FOR_REQUEST, which is True by default.

    +

    Supported configurations:

    +
      +
    • DB_HOST

    • +
    • DB_PORT

    • +
    • DB_USER

    • +
    • DB_PASSWORD

    • +
    • DB_DATABASE

    • +
    • DB_ECHO

    • +
    • DB_POOL_MIN_SIZE

    • +
    • DB_POOL_MAX_SIZE

    • +
    • DB_SSL

    • +
    • DB_USE_CONNECTION_FOR_REQUEST

    • +
    • DB_KWARGS

    • +
    +

    An example server:

    +
    from sanic import Sanic
    +from sanic.exceptions import abort
    +from sanic.response import json
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_DATABASE = 'gino'
    +db = Gino()
    +db.init_app(app)
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode())
    +
    +    def __repr__(self):
    +        return '{}<{}>'.format(self.nickname, self.id)
    +
    +
    +@app.route("/users/<user_id>")
    +async def get_user(request, user_id):
    +    if not user_id.isdigit():
    +        abort(400, 'invalid user id')
    +    user = await User.get_or_404(int(user_id))
    +    return json({'name': user.nickname})
    +
    +
    +if __name__ == '__main__':
    +    app.run(debug=True)
    +
    +
    +
    +
    +

    Sanic Support

    +

    To integrate with Sanic, a few configurations needs to be set in +app.config (with default value though):

    +
      +
    • DB_HOST: if not set, localhost

    • +
    • DB_PORT: if not set, 5432

    • +
    • DB_USER: if not set, postgres

    • +
    • DB_PASSWORD: if not set, empty string

    • +
    • DB_DATABASE: if not set, postgres

    • +
    • DB_ECHO: if not set, False

    • +
    • DB_POOL_MIN_SIZE: if not set, 5

    • +
    • DB_POOL_MAX_SIZE: if not set, 10

    • +
    • DB_SSL: if not set, None

    • +
    • DB_KWARGS; if not set, empty dictionary

    • +
    +

    An example:

    +
    from sanic import Sanic
    +from gino.ext.sanic import Gino
    +
    +app = Sanic()
    +app.config.DB_HOST = 'localhost'
    +app.config.DB_USER = 'postgres'
    +
    +db = Gino()
    +db.init_app(app)
    +
    +
    +

    After db.init_app, a connection pool with configured settings shall be +created and bound to db when Sanic server is started, and closed on stop. +Furthermore, a lazy connection context is created on each request, and released +on response. That is to say, within Sanic request handlers, you can directly +access db by e.g. User.get(1), everything else is settled: database pool is +created on server start, connection is lazily borrowed from pool on the first +database access and shared within the rest of the same request handler, and +automatically returned to the pool on response.

    +

    Please be noted that, in the async world, await may block unpredictably for +a long time. When this world is crossing RDBMS pools and transactions, it is +a very dangerous bite for performance, even causing disasters sometimes. +Therefore we recommend, during the time enjoying fast development, do pay +special attention to the scope of transactions and borrowed connections, make +sure that transactions are closed as soon as possible, and connections are not +taken for unnecessarily long time. As for the Sanic support, if you want to +release the concrete connection in the request context before response is +reached, just do it like this:

    +
    await request['connection'].release()
    +
    +
    +

    Or if you prefer not to use the contextual lazy connection in certain handlers, +prefer explicitly manage the connection lifetime, you can always borrow a new +connection by setting reuse=False:

    +
    async with db.acquire(reuse=False):
    +    # new connection context is created
    +
    +
    +

    Or if you prefer not to use the builtin request-scoped lazy connection at all, +you can simply turn it off:

    +
    app.config.DB_USE_CONNECTION_FOR_REQUEST = False
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/extensions/starlette.html b/docs/zh/master/reference/extensions/starlette.html new file mode 100644 index 0000000..9bbc5ca --- /dev/null +++ b/docs/zh/master/reference/extensions/starlette.html @@ -0,0 +1,332 @@ + + + + + + + + Starlette Support - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Starlette Support

    +
    +

    Work with Starlette

    +

    To use GINO with Starlette, the gino-starlette package should +be installed first:

    +
    pip install gino-starlette
    +
    +
    +
    +

    注解

    +

    The gino-starlette package supports only GINO 1.0 or later. +Earlier versions of GINO like 0.8.x have built-in Starlette support.

    +
    +

    This extension provides a Starlette middleware to setup and cleanup +database according to the configurations that passed in the kwargs +parameter.

    +

    The common usage looks like this:

    +
    from starlette.applications import Starlette
    +from gino.ext.starlette import Gino
    +
    +app = Starlette()
    +db = Gino(app, **kwargs)
    +
    +# Or with application factory
    +app = Starlette()
    +db = Gino(**kwargs)
    +db.init_app(app)
    +
    +
    +
    +
    +

    Configuration

    +

    The config includes:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Default

    driver

    the database driver

    asyncpg

    host

    database server host

    localhost

    port

    database server port

    5432

    user

    database server user

    postgres

    password

    database server password

    empty

    database

    database name

    postgres

    dsn

    a SQLAlchemy database URL to create the engine, its existence will replace all previous connect arguments.

    N/A

    pool_min_size

    the initial number of connections of the db pool.

    N/A

    pool_max_size

    the maximum number of connections in the db pool.

    N/A

    echo

    enable SQLAlchemy echo mode.

    N/A

    ssl

    SSL context passed to asyncpg.connect

    None

    use_connection_for_request

    flag to set up lazy connection for requests.

    N/A

    kwargs

    other parameters passed to the specified dialects, like asyncpg. Unrecognized parameters will cause exceptions.

    N/A

    +
    +
    +

    Lazy Connection

    +

    If use_connection_for_request is set to be True, then a lazy +connection is available at request['connection']. By default, a +database connection is borrowed on the first query, shared in the same +execution context, and returned to the pool on response. If you need to +release the connection early in the middle to do some long-running +tasks, you can simply do this:

    +
    await request['connection'].release(permanent=False)
    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/extensions/tornado.html b/docs/zh/master/reference/extensions/tornado.html new file mode 100644 index 0000000..36f161d --- /dev/null +++ b/docs/zh/master/reference/extensions/tornado.html @@ -0,0 +1,214 @@ + + + + + + + + Tornado Support - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    Tornado Support

    +

    THIS IS A WIP

    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/reference/history.html b/docs/zh/master/reference/history.html new file mode 100644 index 0000000..7273099 --- /dev/null +++ b/docs/zh/master/reference/history.html @@ -0,0 +1,928 @@ + + + + + + + + 版本历史 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    版本历史

    +
    +

    GINO 1.1

    +
    +

    1.1.0 (pending)

    +
      +
    • Added baked query feature (#478 #659 #667)

    • +
    • Added Query.gino.execution_options shortcut (#659)

    • +
    • Added @db.declared_attr(with_table=True) (#659)

    • +
    • [Breaking] Empty object instead of None being returned for objects with values of all selected columns are None (#729)

    • +
    • Added MySQL support (#381 #685)

    • +
    • [Breaking] asyncpg is no longer installed as a dependency by default, install gino[pg] for the old behavior

    • +
    • Fixed multiple referenced connection stack in newly created coroutines (#747)

    • +
    +
    +
    +
    +

    GINO 1.0

    +
    +

    Migrating to GINO 1.0

    +

    GINO 1.0 moved the built-in Web framework extensions into separate PyPI +packages. If you're using one of them, you should install GINO 1.0 with extras:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +

    Extension Module

    Installation in GINO 1.0

    gino.ext.starlette

    pip install gino[starlette]

    gino.ext.aiohttp

    pip install gino[aiohttp]

    gino.ext.sanic

    pip install gino[sanic]

    gino.ext.tornado

    pip install gino[tornado]

    gino.ext.quart

    pip install gino[quart]

    +

    The new extension packages are backward-compatible, so there's no need to +update the import statements. For example, this will still work in GINO 1.0 if +you installed gino[starlette]:

    +
    from gino.ext.starlette import Gino
    +
    +
    +

    GINO 1.0 switched to Poetry for package and +dependency management and started to use the +src layout. This shouldn't +cause any problem using GINO as a dependency, but it does introduce some +changes to the GINO development process:

    +
      +
    • Source files are now located under src directory.

    • +
    • The src dist on PyPI does not include tests, docs and some other files due to +a limitation of Poetry.

    • +
    +
    +
    +

    1.0.1 (2020-06-08)

    +
      +
    • Fixed dependency version range (SQLAlchemy >=1.2.16, <1.4)

    • +
    • Updated docs (Including contribution by Galden in #660, and Iuliia Volkova in #672)

    • +
    • Multiple JSON property fixes (#661 #662 #695)

    • +
    • Fixed extension typing issue (#673 #674)

    • +
    • Fixed model override behavior (#694)

    • +
    • Fixed multiple JSON profiles issue (Contributed by Roman Averchenkov in #693 #696)

    • +
    +
    +
    +

    1.0.0 (2020-04-26)

    +
      +
    • Switched to Poetry for package and dependency management.

    • +
    • [Breaking] Moved built-in extension modules to separate PyPI packages.

    • +
    • Switched to src layout.

    • +
    • Switched to black code style.

    • +
    • Better documentation.

    • +
    • [Breaking] none_as_none() is now always enabled.

    • +
    • Added representation method for engine.

    • +
    • Protected the URL instance fed to set_bind() from manipulation.

    • +
    • Replaced some assert with AssertionError (#258 #655)

    • +
    +
    +
    +
    +

    GINO 0.8

    +

    This is also version 1.0 release candidate.

    +
    +

    Migrating to GINO 0.8

    +
    +

    1. contextvars

    +

    We introduced aiocontextvars 0.2.0 which is revamped to be compatible with +PEP-567 without manual interference by a few simple implicit patches. To +upgrade to GINO 0.8, please remove the enable_inherit() or +disable_inherit() calls, because they are the default behavior now thus +no longer exist. However, you'll need to confirm that the event loop in use is +always created after importing gino or aiocontextvars, or the patch +won't work correctly.

    +

    There is nothing to worry about in Python 3.7.

    +
    +
    +

    2. none_as_none

    +

    When GINO tries to load a row with all NULL values into an instance, it +will now by default return None instead of an instance with all None +attributes. To recover the default behavior of 0.7, please specify +none_as_none(False) in affected model loader.

    +

    This is especially applicable to relationship sub-loaders - if the sub-loader +found it all NULL, no instance will be set to parent instance. For +example:

    +
    child = await Child.load(parent=Parent).query.gino.first()
    +
    +
    +

    If child.parent_id is NULL in database, then the child instance +won't be called with any setattr(child, 'parent', ...) at all. (If you need +child.parent == None in this case, consider setting default value +parent = None in child model.)

    +

    Please note, it is deprecated to disable none_as_none, and disabling will +be removed in GINO 1.0.

    +
    +
    +
    +

    0.8.7 (2020-04-19)

    +
      +
    • Improved error handling when attribute names collide (Contributed by Reskov in #637 #638)

    • +
    • Fixed with_bind usability in aiohttp extension (#518)

    • +
    +
    +
    +

    0.8.6 (2020-02-10)

    +
      +
    • Fixed JSONPathType bind processor for asyncpg (#609)

    • +
    • Allowed for primary keys with different names from the database columns (Contributed by Tiago Requeijo in #599 #600)

    • +
    • Allowed to use simple callable instead of property for one-2-many loading (Contributed by Olexiy in #629)

    • +
    • Added distinct() to Alias (#628)

    • +
    +
    +
    +

    0.8.5 (2019-11-19)

    +
      +
    • Improved support for __tablename__ in declared_attr (Contributed by Roald Storm in #592)

    • +
    +
    +
    +

    0.8.4 (2019-11-09)

    +
      +
    • Better loader support for models in subqueries (#573 #585)

    • +
    • Allowed __tablename__ to be a declared_attr (#579 #582)

    • +
    • Fixed Sanic 19.9.0 compatibility (#569)

    • +
    • Added one() and one_or_none() (Contributed by Ilaï Deutel in #577)

    • +
    • Improved Starleet extension compatibility (Contributed by Jim O'Brien in #538)

    • +
    • Fixed Starlette connection release during exceptions issue (Contributed by qulaz in #533)

    • +
    • Fixed server event compatibility with Sanic 19.6.2 (Contributed by Julio Lacerda in #520)

    • +
    • Fixed Grammar (Contributed by Simeon J Morgan in #504)

    • +
    +
    +
    +

    0.8.3 (2019-06-06)

    +
      +
    • Fixed deprecated warnings in asyncpg dialect and aiohttp (#425)

    • +
    • Customizable db attribute name in aiohttp app instance (#457)

    • +
    • Added Starlette support (#486)

    • +
    +
    +
    +

    0.8.2 (2019-03-07)

    +
      +
    • Added exception for unknown JSON properties (#406 #408)

    • +
    • Supported Quart 0.7 (#411)

    • +
    • Accepted kwargs for db init in extensions (#407 #427)

    • +
    • Added custom config parameter to aiohttp middleware (Contributed by Michał Dziewulski in #440)

    • +
    • Added NullPool (#437 #441)

    • +
    • Unpinned dependency versions (#447)

    • +
    • Added support for SQLAlchemy 1.3 (#433 #451)

    • +
    +
    +
    +

    0.8.1 (2018-12-08)

    +
      +
    • Alias supported Label (#365)

    • +
    • Docs update (#308, 4c59ad, #401 by Pascal van Kooten)

    • +
    • Version requirement for SQLAlchemy is updated to >=1.2 (#378 #382)

    • +
    • Added option for SSL in aiohttp extension (Contributed by Martin Zaťko in #387 #393) +* And all other extensions (#395)

    • +
    • Supported Tornado 5 (#396, also thanks to Vladimir Goncharov)

    • +
    • Fixed custom JSON/JSONB type support (#402 #403)

    • +
    +

    (Most fixes done by Tony Wang)

    +
    +
    +

    0.8.0 (2018-10-16)

    +
      +
    • Welcome Tony Wang to the maintenance team (#335)

    • +
    • Allowed custom column names (#261 #297)

    • +
    • Allowed column instance in model.load() (Contributed by Jekel in #323)

    • +
    • [Breaking] Upgraded to aiocontextvars 0.2.0 (#333)

    • +
    • Fixed bug that the same empty stack is shared between sub-tasks (#313 #334)

    • +
    • [Breaking] Made none_as_none() the default behavior (#351)

    • +
    • Bug fixes and docs update

    • +
    +
    +
    +
    +

    GINO 0.7

    +

    This is also version 1.0 beta 3.

    +
    +

    0.7.7 (2018-12-08)

    +
      +
    • Backported fix for custom JSON/JSONB type support (#402 #403)

    • +
    +
    +
    +

    0.7.6 (2018-08-26)

    +
      +
    • Updated library support (Contributed by Tony Wang in #275 #309)

    • +
    • Added none_as_none() (#281 #282)

    • +
    • Added ARRAY alias in asyncpg dialect module (Contributed by Mykyta Holubakha in #289)

    • +
    • Added Model.lookup() to prevent updating whole table without primary key (#287 #288)

    • +
    • Added DB_ECHO in extension options (Contributed by Mykyta Holubakha in #298)

    • +
    • Fixed broken JSON/JSONB result processor since 0.5.8 (Contributed by Tony Wang in #291 #305)

    • +
    • Removed bad rollback after a failing commit (Contributed by Tony Wang in #302 #304)

    • +
    • Fixed to raise UninitializedError if bind is None (Contributed by Tony Wang in #307 #310)

    • +
    +
    +
    +

    0.7.5 (2018-07-26)

    +
      +
    • Added friendly error message when using abstract models by mistake (#224)

    • +
    • Supported Python 3.7 (Contributed by Tony Wang in #265)

    • +
    • Updated documentation

    • +
    • Fixed a bug in TupleLoader (Contributed by Pavol Vargovcik in #279 #280)

    • +
    +
    +
    +

    0.7.4 (2018-06-10)

    +
      +
    • Added aiocontextvars as required dependency required for Python 3.5 and 3.6 (#228)

    • +
    • Added Quart support (#213)

    • +
    • Fixed Tornado options parsing (#231)

    • +
    • Improved coding style and test coverage

    • +
    +
    +
    +

    0.7.3 (2018-05-19)

    +
      +
    • Fix for failing binary type (#225)

    • +
    +
    +
    +

    0.7.2 (2018-05-15)

    +
      +
    • Added prepared statement support (#14)

    • +
    • Added dsn in extension config (Contributed by Yurii Shtrikker in #215)

    • +
    +
    +
    +

    0.7.1 (2018-05-03)

    +
      +
    • Added support for inline model constraints (Contributed by Kinware in #198)

    • +
    • Added docs and tests for using SSL (#202)

    • +
    • Added declared_attr (#204)

    • +
    • Allowed ModelLoader passively load partial model (#216)

    • +
    +
    +
    +

    0.7.0 (2018-04-18)

    +
      +
    • Added Python 3.5 support (#187)

    • +
    • Added support to use dict as ident for Model.get (#192)

    • +
    • Added result loader (partial relationship support) (#13)

    • +
    • Added documentation on relationship and transaction (#146)

    • +
    +
    +
    +
    +

    GINO 0.6

    +

    This is also version 1.0 beta 2.

    +
    +

    Migrating to GINO 0.6

    +
    +

    1. Task Local

    +

    We created a new Python package aiocontextvars from previous local.py. If +you made use of the task local features, you should install this package.

    +

    Previous gino.enable_task_local() and gino.disable_task_local() are +replaced by aiocontextvars.enable_inherit() and +aiocontextvars.disable_inherit(). However in GINO 0.5 they controls the +whole task local feature switch, while aiocontextvars by default offers task +local even without enable_inherit(), which controls whether the local +storage should be passed between chained tasks. When enabled, it behaves the +same as enabled in 0.5, but you cannot completely turn off the task local +feature while aiocontextvars is installed.

    +

    There is no gino.get_local() and gino.reset_local() relevant in +aiocontextvars. The similar thing is aiocontextvars.ContextVar instance +through its get(), set() and delete() methods.

    +

    Previous gino.is_local_root() is now +not aiocontextvars.Context.current().inherited.

    +
    +
    +

    2. Engine

    +

    GINO 0.6 hides asyncpg.Pool behind the new SQLAlchemy-alike +gino.GinoEngine. Instead of doing this in 0.5:

    +
    async with db.create_pool('postgresql://...') as pool:
    +    # your code here
    +
    +
    +

    You should change it to this in 0.6:

    +
    async with db.with_bind('postgresql://...') as engine:
    +    # your code here
    +
    +
    +

    This equals to:

    +
    engine = await gino.create_engine('postgresql://...')
    +db.bind = engine
    +try:
    +    # your code here
    +finally:
    +    db.bind = None
    +    await engine.close()
    +
    +
    +

    Or:

    +
    engine = await db.set_bind('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Or even this:

    +
    db = await gino.Gino('postgresql://...')
    +try:
    +    # your code here
    +finally:
    +    await db.pop_bind().close()
    +
    +
    +

    Choose whichever suits you the best.

    +

    Obviously GinoEngine doesn't provide asyncpg.Pool methods directly any +longer, but you can get the underlying asyncpg.Pool object through +engine.raw_pool property.

    +

    GinoPool.get_current_connection() is now changed to current_connection +property on GinoEngine instances to support multiple engines.

    +

    GinoPool.execution_option is gone, instead update_execution_options() +on GinoEngine instance is available.

    +

    GinoPool().metadata is gone, dialect is still available.

    +

    GinoPool.release() is removed in GinoEngine and Gino, the +release() method on GinoConnection object should be used instead.

    +

    These methods exist both in 0.5 GinoPool and 0.6 GinoEngine: +close(), acquire(), all(), first(), scalar(), status().

    +
    +
    +

    3. GinoConnection

    +

    Similarly, GinoConnection in 0.6 is no longer a subclass of +asyncpg.Connection, instead it has a asyncpg.Connection instance, +accessable through GinoConnection.raw_connection property.

    +

    GinoConnection.metadata is deleted in 0.6, while dialect remained.

    +

    GinoConnection.execution_options() is changed from a mutable dict in 0.5 to +a method returning a copy of current connection with the new options, the same +as SQLAlchemy behavior.

    +

    GinoConnection.release() is still present, but its default behavior has +been changed to permanently release this connection. You should add argument +permanent=False to remain its previous behavior.

    +

    And all(), first(), scalar(), status(), iterate(), +transaction() remained in 0.6.

    +
    +
    +

    4. Query API

    +

    All five query APIs all(), first(), scalar(), status(), +iterate() now accept the same parameters as SQLAlchemy execute(), +meaning they accept raw SQL text, or multiple sets of parameters for +"executemany". Please note, if the parameters are recognized as "executemany", +none of the methods will return anything. Meanwhile, they no longer accept the +parameter bind if they did. Just use the API on the GinoEngine or +GinoConnection object instead.

    +
    +
    +

    5. Transaction

    +

    Transaction interface is rewritten. Now in 0.6, a GinoTransaction object is +provided consistently from all 3 methods:

    +
    async with db.transaction() as tx:
    +    # within transaction
    +
    +async with engine.transaction() as tx:
    +    # within transaction
    +
    +async with engine.acquire() as conn:
    +    async with conn.transaction() as tx:
    +        # within transaction
    +
    +
    +

    And different usage with await:

    +
    tx = await db.transaction()
    +try:
    +    # within transaction
    +    await tx.commit()
    +except:
    +    await tx.rollback()
    +    raise
    +
    +
    +

    The GinoConnection object is available at tx.connection, while +underlying transaction object from database driver is available at +tx.transaction - for asyncpg it is an asyncpg.transaction.Transaction +object.

    +
    +
    +
    +

    0.6.6 (2018-05-18)

    +
      +
    • Backported a fix for failing binary type (#225)

    • +
    +
    +
    +

    0.6.5 (2018-04-18)

    +
      +
    • Abandoned 0.6.4 and keep 0.6.x stable

    • +
    • Backported doc for transaction

    • +
    +
    +
    +

    0.6.4 (2018-04-16)

    +

    Abandoned version, please use 0.7.0 instead.

    +
    +
    +

    0.6.3 (2018-04-08)

    +
      +
    • Added aiohttp support

    • +
    • Added support for calling create() on model instances (Contributed by Kinware in #178 #180)

    • +
    • Fixed get() by string, and misc environment issues (Contributed by Tony Wang in #191 193 #183 #184)

    • +
    +
    +
    +

    0.6.2 (2018-03-24)

    +
      +
    • Fixed SQLAlchemy prefetch issue (#141)

    • +
    • Fixed issue that mixin class on Model not working (#174)

    • +
    • Added more documentation (Thanks Olaf Conradi for reviewing)

    • +
    +
    +
    +

    0.6.1 (2018-03-18)

    +
      +
    • Fixed create and drop for Enum type (#160)

    • +
    • A bit more documentation (#159)

    • +
    +
    +
    +

    0.6.0 (2018-03-14)

    +
      +
    • [Breaking] API Refactored, Pool replaced with Engine

      +
        +
      • New API Engine replaced asyncpg Pool (#59)

      • +
      • Supported different dialects, theoretically

      • +
      • Used aiocontextvars instead of builtin task local (#89)

      • +
      +
    • +
    • [Breaking] Fixed query API with multiparams (executemany) to return correctly (#20)

    • +
    • [Breaking] The query methods no longer accept the parameter bind

    • +
    • [Breaking] Gino no longer exposes postgresql types

    • +
    • Added echo on engine (#142)

    • +
    • Added tests to cover 80% of code

    • +
    • Added gino extension on SchemaItem for create_all and so on (#76 #106)

    • +
    • Added gino extension on model classes for create() or drop()

    • +
    • Added _update_request_cls on CRUDModel (#147)

    • +
    • Rewrote the documentation (#146)

    • +
    +
    +
    +
    +

    GINO 0.5

    +

    This is also version 1.0 beta 1.

    +
    +

    0.5.8 (2018-02-14)

    +
      +
    • Preparing for 0.6.0 which will be a breaking release

    • +
    • Fixed wrong value of Enum in creation (Contributed by Sergey Kovalev in #126)

    • +
    +
    +
    +

    0.5.7 (2017-11-24)

    +

    This is an emergency fix for 0.5.6.

    +
      +
    • Fixed broken lazy connection (Contributed by Ádám Barancsuk in #114)

    • +
    • Added Model.outerjoin

    • +
    +
    +
    +

    0.5.6 (2017-11-23)

    +
      +
    • Changed to use unnamed statement when possible (#80 #90)

    • +
    • Added more example (Contributed by Kentoseth in #109)

    • +
    • Added Model.join and made Model selectable (Contributed by Ádám Barancsuk in #112 #113)

    • +
    +
    +
    +

    0.5.5 (2017-10-18)

    +
      +
    • Ensured clean connection if transaction acquire fails (Contributed by Vladimir Goncharov in #87)

    • +
    • Added ability to reset local storage (#84)

    • +
    • Fixed bug in JSON property update

    • +
    • Added update chaining feature

    • +
    +
    +
    +

    0.5.4 (2017-10-04)

    +
      +
    • Updated example (Contributed by Kinware in #75)

    • +
    • Added Model.insert (Contributed by Neal Wang in #63)

    • +
    • Fixed issue that non-lazy acquiring fails dirty (#79)

    • +
    +
    +
    +

    0.5.3 (2017-09-23)

    +
      +
    • Fixed no module named cutils error (Contributed by Vladimir Goncharov in #73)

    • +
    +
    +
    +

    0.5.2 (2017-09-10)

    +
      +
    • Added missing driver name on dialect (#67)

    • +
    • Fixed dialect to support native decimal type (#67)

    • +
    +
    +
    +

    0.5.1 (2017-09-09)

    +

    This is an emergency fix for 0.5.0.

    +
      +
    • Reverted the extension, back to pure Python (#60)

    • +
    • Used SQLAlchemy RowProxy

    • +
    • Added first_or_404

    • +
    • Fixed bug that GinoPool cannot be inherited

    • +
    +
    +
    +

    0.5.0 (2017-09-03)

    +
      +
    • [Breaking] Internal refactor: extracted and isolated a few modules, partially rewritten

      +
        +
      • Extracted CRUD operations

      • +
      • Core operations are moved to dialect and execution context

      • +
      • Removed guess_model, switched to explicit execution options

      • +
      • Turned timeout parameter to an execution option

      • +
      • Extracted pool, connection and api from asyncpg_delegate

      • +
      +
    • +
    • Added support for SQLAlchemy execution options, and a few custom options

    • +
    • [Breaking] Made Model.select return rows by default (#39)

    • +
    • Moved get_or_404 to extensions (#38)

    • +
    • Added iterator on model classes (#43)

    • +
    • Added Tornado extension (Contributed by Vladimir Goncharov)

    • +
    • Added Model.to_dict (#47)

    • +
    • Added an extension module to update asyncpg.Record with processed results

    • +
    +
    +
    +
    +

    Early Development Releases

    +

    Considered as alpha releases.

    +
    +

    0.4.1 (2017-08-20)

    +
      +
    • Support select on model instance

    • +
    +
    +
    +

    0.4.0 (2017-08-15)

    +
      +
    • Made get_or_404 more friendly when Sanic is missing (Contributed by Neal Wang in #23 #31)

    • +
    • Delegated sqlalchemy.__all__ (Contributed by Neal Wang in #10 #33)

    • +
    • [Breaking] Rewrote JSON/JSONB support (#29)

    • +
    • Added lazy parameter on db.acquire (Contributed by Binghan Li in #32)

    • +
    • Added Sanic integration (Contributed by Binghan Li, Tony Wang in #30 #32 #34)

    • +
    • Fixed iterate API to be compatible with asyncpg (#32)

    • +
    • Unified exceptions

    • +
    • [Breaking] Changed update API (#29)

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.3.0 (2017-08-07)

    +
      +
    • Supported __table_args__ (#12)

    • +
    • Introduced task local to manage connection in context (#19)

    • +
    • Added query.gino extension for in-place execution

    • +
    • Refreshed README (#3)

    • +
    • Adopted PEP 487 (Contributed by Tony Wang in #17 #27)

    • +
    • Used weakref on __model__ of table and query (Contributed by Tony Wang)

    • +
    • Delegated asyncpg timeout parameter (Contributed by Neal Wang in #16 #22)

    • +
    +
    +
    +

    0.2.3 (2017-08-04)

    +
      +
    • Supported any primary key (Contributed by Tony Wang in #11)

    • +
    +
    +
    +

    0.2.2 (2017-08-02)

    +
      +
    • Supported SQLAlchemy result processor

    • +
    • Added rich support on JSON/JSONB

    • +
    • Bug fixes

    • +
    +
    +
    +

    0.2.1 (2017-07-28)

    +
      +
    • Added update and delete API

    • +
    +
    +
    +

    0.2.0 (2017-07-28)

    +
      +
    • Changed API, no longer reuses asyncpg API

    • +
    +
    +
    +

    0.1.1 (2017-07-25)

    +
      +
    • Added db.bind

    • +
    • API changed: parameter conn renamed to optional bind

    • +
    • Delegated asyncpg Pool with db.create_pool

    • +
    • Internal enhancement and bug fixes

    • +
    +
    +
    +

    0.1.0 (2017-07-21)

    +
      +
    • First release on PyPI.

    • +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + + + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/search.html b/docs/zh/master/search.html new file mode 100644 index 0000000..2500d6c --- /dev/null +++ b/docs/zh/master/search.html @@ -0,0 +1 @@ +

    search.html

    \ No newline at end of file diff --git a/docs/zh/master/searchindex.js b/docs/zh/master/searchindex.js new file mode 100644 index 0000000..52a170a --- /dev/null +++ b/docs/zh/master/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["explanation","explanation/async","explanation/engine","explanation/sa20","explanation/why","how-to","how-to/alembic","how-to/bakery","how-to/contributing","how-to/crud","how-to/faq","how-to/json-props","how-to/loaders","how-to/pool","how-to/schema","how-to/transaction","index","reference","reference/api","reference/api/gino","reference/api/gino.aiocontextvars","reference/api/gino.api","reference/api/gino.bakery","reference/api/gino.crud","reference/api/gino.declarative","reference/api/gino.dialects","reference/api/gino.dialects.aiomysql","reference/api/gino.dialects.asyncpg","reference/api/gino.dialects.base","reference/api/gino.engine","reference/api/gino.exceptions","reference/api/gino.ext","reference/api/gino.json_support","reference/api/gino.loader","reference/api/gino.schema","reference/api/gino.strategies","reference/api/gino.transaction","reference/extensions","reference/extensions/sanic","reference/extensions/starlette","reference/extensions/tornado","reference/history","tutorials","tutorials/announcement","tutorials/fastapi","tutorials/tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["explanation.rst","explanation/async.rst","explanation/engine.rst","explanation/sa20.rst","explanation/why.rst","how-to.rst","how-to/alembic.rst","how-to/bakery.rst","how-to/contributing.rst","how-to/crud.rst","how-to/faq.rst","how-to/json-props.rst","how-to/loaders.rst","how-to/pool.rst","how-to/schema.rst","how-to/transaction.rst","index.rst","reference.rst","reference/api.rst","reference/api/gino.rst","reference/api/gino.aiocontextvars.rst","reference/api/gino.api.rst","reference/api/gino.bakery.rst","reference/api/gino.crud.rst","reference/api/gino.declarative.rst","reference/api/gino.dialects.rst","reference/api/gino.dialects.aiomysql.rst","reference/api/gino.dialects.asyncpg.rst","reference/api/gino.dialects.base.rst","reference/api/gino.engine.rst","reference/api/gino.exceptions.rst","reference/api/gino.ext.rst","reference/api/gino.json_support.rst","reference/api/gino.loader.rst","reference/api/gino.schema.rst","reference/api/gino.strategies.rst","reference/api/gino.transaction.rst","reference/extensions.rst","reference/extensions/sanic.rst","reference/extensions/starlette.rst","reference/extensions/tornado.rst","reference/history.rst","tutorials.rst","tutorials/announcement.rst","tutorials/fastapi.rst","tutorials/tutorial.rst"],objects:{"":[[19,0,0,"-","gino"]],"gino.aiocontextvars":[[20,1,1,"","patch_asyncio"]],"gino.api":[[21,2,1,"","Gino"],[21,2,1,"","GinoExecutor"]],"gino.api.Gino":[[21,3,1,"","Model"],[21,4,1,"","acquire"],[21,4,1,"","all"],[21,4,1,"","bake"],[21,3,1,"","bakery"],[21,3,1,"","bind"],[21,4,1,"","compile"],[21,4,1,"","first"],[21,4,1,"","iterate"],[21,5,1,"","model_base_classes"],[21,5,1,"","no_delegate"],[21,4,1,"","one"],[21,4,1,"","one_or_none"],[21,4,1,"","pop_bind"],[21,5,1,"","query_executor"],[21,4,1,"","scalar"],[21,5,1,"","schema_visitor"],[21,4,1,"","set_bind"],[21,4,1,"","status"],[21,4,1,"","transaction"],[21,4,1,"","with_bind"]],"gino.api.GinoExecutor":[[21,4,1,"","all"],[21,4,1,"","execution_options"],[21,4,1,"","first"],[21,4,1,"","iterate"],[21,4,1,"","load"],[21,4,1,"","model"],[21,4,1,"","one"],[21,4,1,"","one_or_none"],[21,3,1,"","query"],[21,4,1,"","return_model"],[21,4,1,"","scalar"],[21,4,1,"","status"],[21,4,1,"","timeout"]],"gino.bakery":[[22,2,1,"","BakedQuery"],[22,2,1,"","Bakery"]],"gino.bakery.BakedQuery":[[22,3,1,"","bind"],[22,3,1,"","compiled_sql"],[22,4,1,"","execution_options"],[22,4,1,"","get"],[22,3,1,"","query"],[22,3,1,"","sql"]],"gino.bakery.Bakery":[[22,4,1,"","bake"],[22,5,1,"","query_cls"]],"gino.crud":[[23,2,1,"","Alias"],[23,2,1,"","CRUDModel"],[23,2,1,"","QueryModel"],[23,2,1,"","UpdateRequest"]],"gino.crud.Alias":[[23,4,1,"","distinct"],[23,4,1,"","load"],[23,4,1,"","on"]],"gino.crud.CRUDModel":[[23,4,1,"","alias"],[23,4,1,"","append_where_primary_key"],[23,4,1,"","create"],[23,5,1,"","delete"],[23,4,1,"","distinct"],[23,4,1,"","get"],[23,4,1,"","in_query"],[23,4,1,"","load"],[23,4,1,"","lookup"],[23,4,1,"","none_as_none"],[23,4,1,"","on"],[23,5,1,"","query"],[23,4,1,"","select"],[23,4,1,"","to_dict"],[23,5,1,"","update"]],"gino.crud.UpdateRequest":[[23,4,1,"","apply"],[23,4,1,"","update"]],"gino.declarative":[[24,2,1,"","ColumnAttribute"],[24,2,1,"","InvertDict"],[24,2,1,"","Model"],[24,1,1,"","declarative_base"],[24,1,1,"","declared_attr"]],"gino.declarative.InvertDict":[[24,4,1,"","invert_get"]],"gino.dialects":[[26,0,0,"-","aiomysql"],[27,0,0,"-","asyncpg"],[28,0,0,"-","base"]],"gino.dialects.aiomysql":[[26,2,1,"","AiomysqlDBAPI"],[26,2,1,"","AiomysqlDialect"],[26,2,1,"","AiomysqlExecutionContext"],[26,2,1,"","AiomysqlIterator"],[26,2,1,"","AsyncEnum"],[26,2,1,"","DBAPICursor"],[26,2,1,"","GinoNullType"],[26,2,1,"","Pool"],[26,2,1,"","Transaction"]],"gino.dialects.aiomysql.AiomysqlDBAPI":[[26,5,1,"","paramstyle"]],"gino.dialects.aiomysql.AiomysqlDialect":[[26,5,1,"","colspecs"],[26,5,1,"","cursor_cls"],[26,5,1,"","dbapi_class"],[26,5,1,"","driver"],[26,5,1,"","execution_ctx_cls"],[26,4,1,"","get_isolation_level"],[26,4,1,"","has_table"],[26,5,1,"","init_kwargs"],[26,4,1,"","init_pool"],[26,4,1,"","on_connect"],[26,5,1,"","postfetch_lastrowid"],[26,4,1,"","set_isolation_level"],[26,5,1,"","statement_compiler"],[26,5,1,"","support_prepare"],[26,5,1,"","support_returning"],[26,5,1,"","supports_native_decimal"],[26,4,1,"","transaction"]],"gino.dialects.aiomysql.AiomysqlExecutionContext":[[26,4,1,"","get_affected_rows"],[26,4,1,"","get_lastrowid"]],"gino.dialects.aiomysql.AiomysqlIterator":[[26,4,1,"","forward"],[26,4,1,"","many"],[26,4,1,"","next"]],"gino.dialects.aiomysql.AsyncEnum":[[26,4,1,"","create_async"],[26,4,1,"","drop_async"]],"gino.dialects.aiomysql.DBAPICursor":[[26,4,1,"","async_execute"],[26,3,1,"","description"],[26,4,1,"","execute_baked"],[26,4,1,"","get_statusmsg"],[26,4,1,"","iterate"],[26,4,1,"","prepare"]],"gino.dialects.aiomysql.GinoNullType":[[26,4,1,"","result_processor"]],"gino.dialects.aiomysql.Pool":[[26,4,1,"","acquire"],[26,4,1,"","close"],[26,3,1,"","raw_pool"],[26,4,1,"","release"],[26,4,1,"","repr"]],"gino.dialects.aiomysql.Transaction":[[26,4,1,"","begin"],[26,4,1,"","commit"],[26,3,1,"","raw_transaction"],[26,4,1,"","rollback"]],"gino.dialects.asyncpg":[[27,2,1,"","AsyncEnum"],[27,2,1,"","AsyncpgCompiler"],[27,2,1,"","AsyncpgCursor"],[27,2,1,"","AsyncpgDBAPI"],[27,2,1,"","AsyncpgDialect"],[27,2,1,"","AsyncpgExecutionContext"],[27,2,1,"","AsyncpgIterator"],[27,2,1,"","AsyncpgJSONPathType"],[27,2,1,"","DBAPICursor"],[27,2,1,"","GinoNullType"],[27,2,1,"","NullPool"],[27,2,1,"","Pool"],[27,2,1,"","PreparedStatement"],[27,2,1,"","Transaction"]],"gino.dialects.asyncpg.AsyncEnum":[[27,4,1,"","create_async"],[27,4,1,"","drop_async"]],"gino.dialects.asyncpg.AsyncpgCompiler":[[27,3,1,"","bindtemplate"]],"gino.dialects.asyncpg.AsyncpgCursor":[[27,4,1,"","forward"],[27,4,1,"","many"],[27,4,1,"","next"]],"gino.dialects.asyncpg.AsyncpgDBAPI":[[27,5,1,"","Error"]],"gino.dialects.asyncpg.AsyncpgDialect":[[27,5,1,"","colspecs"],[27,5,1,"","cursor_cls"],[27,5,1,"","dbapi_class"],[27,5,1,"","driver"],[27,5,1,"","execution_ctx_cls"],[27,4,1,"","get_isolation_level"],[27,4,1,"","has_schema"],[27,4,1,"","has_sequence"],[27,4,1,"","has_table"],[27,4,1,"","has_type"],[27,5,1,"","init_kwargs"],[27,4,1,"","init_pool"],[27,4,1,"","on_connect"],[27,4,1,"","set_isolation_level"],[27,5,1,"","statement_compiler"],[27,5,1,"","supports_native_decimal"],[27,4,1,"","transaction"]],"gino.dialects.asyncpg.AsyncpgJSONPathType":[[27,4,1,"","bind_processor"]],"gino.dialects.asyncpg.DBAPICursor":[[27,4,1,"","async_execute"],[27,3,1,"","description"],[27,4,1,"","execute_baked"],[27,4,1,"","get_statusmsg"],[27,4,1,"","prepare"]],"gino.dialects.asyncpg.GinoNullType":[[27,4,1,"","result_processor"]],"gino.dialects.asyncpg.NullPool":[[27,4,1,"","acquire"],[27,4,1,"","close"],[27,3,1,"","raw_pool"],[27,4,1,"","release"],[27,4,1,"","repr"]],"gino.dialects.asyncpg.Pool":[[27,4,1,"","acquire"],[27,4,1,"","close"],[27,3,1,"","raw_pool"],[27,4,1,"","release"],[27,4,1,"","repr"]],"gino.dialects.asyncpg.Transaction":[[27,4,1,"","begin"],[27,4,1,"","commit"],[27,3,1,"","raw_transaction"],[27,4,1,"","rollback"]],"gino.dialects.base":[[28,2,1,"","AsyncDialectMixin"],[28,2,1,"","BaseDBAPI"],[28,2,1,"","Cursor"],[28,2,1,"","DBAPICursor"],[28,2,1,"","ExecutionContextOverride"],[28,2,1,"","Pool"],[28,2,1,"","PreparedStatement"],[28,2,1,"","Transaction"]],"gino.dialects.base.AsyncDialectMixin":[[28,4,1,"","compile"],[28,5,1,"","cursor_cls"],[28,4,1,"","dbapi"],[28,5,1,"","dbapi_class"],[28,4,1,"","init_pool"],[28,5,1,"","support_prepare"],[28,5,1,"","support_returning"],[28,4,1,"","transaction"]],"gino.dialects.base.BaseDBAPI":[[28,4,1,"","Binary"],[28,5,1,"","Error"],[28,5,1,"","paramstyle"]],"gino.dialects.base.Cursor":[[28,4,1,"","forward"],[28,4,1,"","many"],[28,4,1,"","next"]],"gino.dialects.base.DBAPICursor":[[28,4,1,"","async_execute"],[28,3,1,"","description"],[28,4,1,"","execute"],[28,4,1,"","execute_baked"],[28,4,1,"","executemany"],[28,4,1,"","get_statusmsg"],[28,4,1,"","prepare"]],"gino.dialects.base.ExecutionContextOverride":[[28,5,1,"","baked_query"],[28,4,1,"","get_affected_rows"],[28,4,1,"","get_lastrowid"],[28,4,1,"","get_result_proxy"],[28,5,1,"","loader"],[28,5,1,"","model"],[28,4,1,"","process_rows"],[28,5,1,"","return_model"],[28,5,1,"","timeout"]],"gino.dialects.base.Pool":[[28,4,1,"","acquire"],[28,4,1,"","close"],[28,3,1,"","raw_pool"],[28,4,1,"","release"],[28,4,1,"","repr"]],"gino.dialects.base.PreparedStatement":[[28,4,1,"","all"],[28,4,1,"","first"],[28,4,1,"","iterate"],[28,4,1,"","scalar"],[28,4,1,"","status"]],"gino.dialects.base.Transaction":[[28,4,1,"","begin"],[28,4,1,"","commit"],[28,3,1,"","raw_transaction"],[28,4,1,"","rollback"]],"gino.engine":[[29,2,1,"","GinoConnection"],[29,2,1,"","GinoEngine"]],"gino.engine.GinoConnection":[[29,4,1,"","all"],[29,3,1,"","dialect"],[29,4,1,"","execution_options"],[29,4,1,"","first"],[29,4,1,"","get_raw_connection"],[29,4,1,"","iterate"],[29,4,1,"","one"],[29,4,1,"","one_or_none"],[29,4,1,"","prepare"],[29,3,1,"","raw_connection"],[29,4,1,"","release"],[29,4,1,"","scalar"],[29,5,1,"","schema_for_object"],[29,4,1,"","status"],[29,4,1,"","transaction"]],"gino.engine.GinoEngine":[[29,4,1,"","acquire"],[29,4,1,"","all"],[29,4,1,"","close"],[29,4,1,"","compile"],[29,5,1,"","connection_cls"],[29,3,1,"","current_connection"],[29,3,1,"","dialect"],[29,4,1,"","first"],[29,4,1,"","iterate"],[29,4,1,"","one"],[29,4,1,"","one_or_none"],[29,3,1,"","raw_pool"],[29,4,1,"","repr"],[29,4,1,"","scalar"],[29,4,1,"","status"],[29,4,1,"","transaction"],[29,4,1,"","update_execution_options"]],"gino.exceptions":[[30,6,1,"","GinoException"],[30,6,1,"","InitializedError"],[30,6,1,"","MultipleResultsFound"],[30,6,1,"","NoResultFound"],[30,6,1,"","NoSuchRowError"],[30,6,1,"","UninitializedError"],[30,6,1,"","UnknownJSONPropertyError"]],"gino.json_support":[[32,2,1,"","ArrayProperty"],[32,2,1,"","BooleanProperty"],[32,2,1,"","DateTimeProperty"],[32,2,1,"","IntegerProperty"],[32,2,1,"","JSONProperty"],[32,2,1,"","ObjectProperty"],[32,2,1,"","StringProperty"]],"gino.json_support.ArrayProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"]],"gino.json_support.BooleanProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","make_expression"]],"gino.json_support.DateTimeProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","make_expression"]],"gino.json_support.IntegerProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","make_expression"]],"gino.json_support.JSONProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"],[32,4,1,"","get_profile"],[32,4,1,"","make_expression"],[32,4,1,"","reload"],[32,4,1,"","save"]],"gino.json_support.ObjectProperty":[[32,4,1,"","decode"],[32,4,1,"","encode"]],"gino.json_support.StringProperty":[[32,4,1,"","make_expression"]],"gino.loader":[[33,2,1,"","AliasLoader"],[33,2,1,"","CallableLoader"],[33,2,1,"","ColumnLoader"],[33,2,1,"","Loader"],[33,2,1,"","ModelLoader"],[33,2,1,"","TupleLoader"],[33,2,1,"","ValueLoader"]],"gino.loader.CallableLoader":[[33,4,1,"","do_load"]],"gino.loader.ColumnLoader":[[33,4,1,"","do_load"]],"gino.loader.Loader":[[33,4,1,"","do_load"],[33,4,1,"","get"],[33,4,1,"","get_columns"],[33,4,1,"","get_from"],[33,3,1,"","query"]],"gino.loader.ModelLoader":[[33,3,1,"","columns"],[33,4,1,"","distinct"],[33,4,1,"","do_load"],[33,4,1,"","get_columns"],[33,4,1,"","get_from"],[33,4,1,"","load"],[33,4,1,"","none_as_none"],[33,4,1,"","on"]],"gino.loader.TupleLoader":[[33,4,1,"","do_load"]],"gino.loader.ValueLoader":[[33,4,1,"","do_load"]],"gino.schema":[[34,2,1,"","AsyncSchemaDropper"],[34,2,1,"","AsyncSchemaGenerator"],[34,2,1,"","AsyncSchemaTypeMixin"],[34,2,1,"","AsyncVisitor"],[34,2,1,"","GinoSchemaVisitor"],[34,1,1,"","patch_schema"]],"gino.schema.AsyncSchemaDropper":[[34,4,1,"","visit_foreign_key_constraint"],[34,4,1,"","visit_index"],[34,4,1,"","visit_metadata"],[34,4,1,"","visit_sequence"],[34,4,1,"","visit_table"]],"gino.schema.AsyncSchemaGenerator":[[34,4,1,"","visit_foreign_key_constraint"],[34,4,1,"","visit_index"],[34,4,1,"","visit_metadata"],[34,4,1,"","visit_sequence"],[34,4,1,"","visit_table"]],"gino.schema.AsyncSchemaTypeMixin":[[34,4,1,"","create_async"],[34,4,1,"","drop_async"]],"gino.schema.AsyncVisitor":[[34,4,1,"","traverse_single"]],"gino.schema.GinoSchemaVisitor":[[34,4,1,"","create"],[34,4,1,"","create_all"],[34,4,1,"","drop"],[34,4,1,"","drop_all"]],"gino.strategies":[[35,2,1,"","GinoStrategy"]],"gino.strategies.GinoStrategy":[[35,4,1,"","create"],[35,5,1,"","engine_cls"],[35,5,1,"","name"]],"gino.transaction":[[36,2,1,"","GinoTransaction"]],"gino.transaction.GinoTransaction":[[36,4,1,"","commit"],[36,3,1,"","connection"],[36,4,1,"","raise_commit"],[36,4,1,"","raise_rollback"],[36,3,1,"","raw_transaction"],[36,4,1,"","rollback"]],gino:[[20,0,0,"-","aiocontextvars"],[21,0,0,"-","api"],[22,0,0,"-","bakery"],[19,1,1,"","create_engine"],[23,0,0,"-","crud"],[24,0,0,"-","declarative"],[25,0,0,"-","dialects"],[29,0,0,"-","engine"],[30,0,0,"-","exceptions"],[31,0,0,"-","ext"],[19,1,1,"","get_version"],[32,0,0,"-","json_support"],[33,0,0,"-","loader"],[34,0,0,"-","schema"],[35,0,0,"-","strategies"],[36,0,0,"-","transaction"]]},objnames:{"0":["py","module","Python \u6a21\u5757"],"1":["py","function","Python \u51fd\u6570"],"2":["py","class","Python \u7c7b"],"3":["py","property","Python property"],"4":["py","method","Python \u65b9\u6cd5"],"5":["py","attribute","Python \u5c5e\u6027"],"6":["py","exception","Python \u4f8b\u5916"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:property","4":"py:method","5":"py:attribute","6":"py:exception"},terms:{"0020":43,"02":[12,17],"03":17,"04":[12,17],"05":17,"06":17,"07":17,"08":[12,17],"09":17,"0x10a8ba860":14,"10":[2,4,10,12,17,29,38,43,45],"100":[3,4,23,44],"106":41,"109":41,"11":[17,43,44],"112":41,"113":[10,41],"114":41,"11th":4,"12":[8,17,43,44],"123":7,"126":41,"127":44,"128":36,"13":[41,43,44],"14":[17,43],"141":41,"142":41,"146":41,"147":41,"15":[17,43],"159":41,"16":[11,17,43,44],"160":41,"17":[11,41,43,45],"174":41,"178":41,"18":[11,17,43],"180":41,"183":41,"184":41,"187":41,"19":[17,43],"191":41,"192":41,"193":41,"198":41,"1990":11,"20":[10,12,17,43,44],"2017":[17,43],"2018":[12,17],"2019":17,"202":41,"2020":[16,17],"204":41,"21":[17,43],"213":41,"215":41,"216":41,"21s":44,"22":[41,43],"224":41,"225":41,"228":41,"23":[12,17,43,44],"231":41,"24":[17,43],"249":3,"25":[17,43],"258":41,"26":[17,43],"261":41,"265":41,"27":[41,43],"275":41,"279":41,"28":[17,43],"280":41,"281":41,"282":41,"287":41,"288":41,"289":41,"29":[41,43],"291":41,"297":41,"298":41,"30":[41,43],"302":41,"304":41,"305":41,"307":41,"308":41,"309":41,"31":[41,43],"310":41,"313":41,"32":[23,36,41,43],"323":41,"32c0feba61ea":44,"32c0feba61ea_add_users_t":44,"33":[41,43],"333":41,"334":41,"335":41,"34":41,"351":41,"365":41,"378":41,"38":41,"381":41,"382":41,"387":41,"39":41,"393":41,"395":41,"396":41,"3apull_request":8,"40":7,"400":38,"401":41,"402":41,"403":41,"404":44,"406":41,"407":41,"408":41,"411":41,"42":23,"425":41,"427":41,"43":41,"431847":12,"433":41,"437":41,"440":41,"441":41,"447":41,"451":41,"457":41,"47":41,"478":41,"486":41,"487":41,"4888":43,"4c59ad":41,"504":41,"518":41,"520":41,"53010":44,"53015":44,"533":41,"538":41,"54":44,"5432":[8,38,39,44],"5433":8,"567":41,"569":41,"573":41,"577":41,"579":41,"582":41,"585":41,"59":41,"592":41,"599":41,"60":41,"600":[8,41],"609":41,"627":43,"628":41,"629":41,"63":41,"63562":44,"63563":44,"637":41,"638":41,"64":36,"655":41,"659":41,"660":41,"661":41,"662":41,"667":41,"67":41,"672":41,"673":41,"674":41,"685":41,"693":41,"694":41,"695":41,"696":41,"729":41,"73":41,"747":41,"75":41,"76":41,"79":41,"80":[41,44],"8000":44,"84":41,"87":[41,43],"89":41,"90":41,"abstract":[4,24,33,41],"boolean":[11,29,33],"break":[1,3,15,41],"case":[2,3,4,6,10,12,13,14,15,21,26,41],"class":[2,6,7,10,11,12,13,14,21,22,23,24,26,27,28,29,32,33,34,35,36,38,41,43,44,45],"default":[2,3,4,6,7,8,11,12,13,14,15,19,21,23,24,26,27,29,32,33,38,39,41,44,45],"do":[1,2,3,4,5,6,8,12,15,29,38,39],"enum":[26,27,41],"export":[6,8],"final":[2,4,10,29,41],"float":[11,26],"for":[0,1,2,4,5,6,7,8,11,12,14,15,19,21,22,23,24,26,27,29,31,33,35,36,38,39,41,43,44,45],"function":[2,8,11,20,22,26,27,29,33],"if":[2,3,4,6,7,8,10,11,12,14,15,20,21,22,23,24,26,27,29,33,35,36,38,39,41,44],"import":[2,3,4,6,7,10,11,12,13,14,20,21,24,31,33,35,38,39,41,43,44,45],"in":[1,2,3,4,5,6,7,8,11,12,13,14,15,19,21,22,23,24,26,27,29,33,36,38,39,41,43,44],"int":[11,15,38,44],"long":[1,2,3,4,15,29,38,39],"new":[2,3,4,6,7,8,10,12,14,21,22,23,24,26,29,33,35,38,41,45],"null":[2,41],"public":14,"return":[2,3,4,7,10,11,12,14,15,21,22,23,24,26,27,29,33,35,38,39,41,44,45],"short":[2,3,4,7,14],"static":28,"super":[4,12],"switch":[2,3,21,41],"transient":2,"true":[2,3,4,6,7,10,11,12,14,15,21,23,24,26,27,28,29,33,34,38,39,41,43,44,45],"try":[2,3,4,10,15,29,36,41],"var":8,"while":[1,2,4,10,12,14,21,23,29,36,41],"with":[1,2,3,4,5,6,8,12,14,15,17,19,21,22,23,24,26,29,33,36,37,41,43,44,45],__:44,__all__:41,__attr_factory__:24,__init__:[12,44],__main__:38,__metadata__:24,__model__:41,__name__:[24,38,44],__repr__:38,__table__:[14,23,24],__table_args__:[24,41,45],__tablename__:[6,7,10,11,12,14,24,38,41,43,44,45],__values__:24,_base:27,_bind:10,_child:12,_children:12,_engin:[26,27],_event:[26,27],_floattyp:26,_idx1:45,_idx2:45,_integertyp:26,_is_metadata_oper:34,_lastrowid:26,_matchtyp:26,_name_idx:10,_numerictyp:26,_parent:12,_pk:45,_sa:26,_schematranslatemap:29,_test:44,_update_request_cl:41,abandon:41,abcd:8,abil:[12,41],abl:[3,4],abnormal_detect:11,abort:38,about:[1,2,4,6,10,14,15,22,23,29,41],abov:[2,3,4,7,10,12,33],absolut:[2,4],accept:[2,3,7,21,23,26,27,29,41],access:[0,2,3,10,14,15,24,29,36,38,41],access_log:11,accessor:26,accid:2,accord:[3,4,29,33,39],achiev:[10,12,14,21,29],acid:3,acquir:[2,3,4,14,21,26,27,28,29,36,38,41],acquisit:4,across:[7,33],act:2,action:[8,36],activ:[7,8,29,44],actual:[2,3,4,7,12,14,15,23,24,29,38],adapt:3,add:[2,3,4,6,8,10,12,21,22,23,31,41,44,45],add_child:12,add_us:44,added:[3,7,19,21,24,29,41,44],addit:[2,3,7,11,12,33],addition:[4,23],address:[6,14],adjac:10,admin:5,ado:7,adopt:41,advanc:5,advic:4,affect:[3,7,22,23,41],after:[1,2,3,4,6,7,14,15,21,23,24,26,27,29,33,38,41,45],after_get:11,afterward:[26,27],again:[2,3,4,10,12,29,45],against:8,age:[11,23,36],age_idx:11,aggreg:23,aintq:43,aiocontextvar:[2,5,17,18,19,41,43],aiohttp:[41,43],aiomysql:[17,18,19,25],aiomysqldbapi:26,aiomysqldialect:26,aiomysqlexecutioncontext:26,aiomysqliter:26,alemb:[5,11,14,21,42,43,45],alembic_sampl:6,ali:10,alia:[10,12,21,22,23,26,27,28,29,33,35,41],aliasload:33,alik:41,all:[1,2,3,4,6,7,8,10,11,12,14,15,21,23,24,26,28,29,33,36,38,39,41,45],all_us:45,allow:[2,3,14,21,24,26,27,41],alon:2,alpha:[10,41],alpin:[8,44],alreadi:[3,4,12,24],alright:1,also:[2,3,4,6,7,10,12,14,15,21,22,23,24,26,27,29,31,33,41],altern:[2,10,13,29],although:[3,12],alwai:[2,3,6,8,10,14,21,23,29,33,36,38,41],amaz:10,among:3,amount:2,an:[1,2,3,4,7,8,10,12,13,14,15,21,22,23,24,26,27,29,31,33,36,38,41,43],and:[2,3,4,5,6,7,8,11,12,13,14,15,21,22,23,24,26,27,29,31,33,35,36,38,39,41],andrei:43,ani:[2,3,4,6,10,21,24,26,27,29,31,33,41],anoth:[2,4,12,22,23,29,33],answer:[3,10],anyth:[4,14,29,41],anywai:3,api:[0,2,4,5,10,14,17,19,22,26,27,29,36,42,43,45],apirout:44,apk:44,app:[4,10,13,38,39,41,44],appear:3,append:23,append_where_primary_kei:23,appli:[2,3,5,14,23,29,36,43,45],applic:[4,7,10,21,39,41,44],appreci:8,approach:[3,4,7,10],arbitrari:4,archlinux:43,are:[1,2,3,4,5,8,10,11,12,13,14,15,21,23,24,29,33,36,38,41,45],arg:[19,21,23,24,26,27,28,29,34,36],argument:[2,3,7,10,12,19,21,22,23,26,27,29,33,35,39,41],around:[3,29,43],arq:43,arrai:[11,27,41],arrayproperti:[11,32],arriv:1,arrrrh:1,articl:3,as:[2,3,4,6,7,10,11,12,13,14,15,21,22,23,24,26,27,29,33,35,36,38,41,44,45],ascend:12,ascii_lett:[10,12],asgi:44,ask:2,assembl:[2,12],assert:[15,24,36,41,44,45],assertionerror:41,assign:[2,26],assist:12,associ:[2,14],assum:[3,4,7,44],assumpt:[3,36],async:[0,1,2,4,7,10,12,14,15,21,23,26,27,28,29,34,35,36,38,41,43,44,45],async_execut:[26,27,28],async_main:3,asyncdialectmixin:[26,27,28],asyncenum:[26,27],asynchron:[0,2,3,12,14,15,21,29,36],asyncio:[0,2,3,5,12,14,16,20,43,45],asyncpg:[2,3,4,10,13,14,15,16,17,18,19,25,29,35,39,41,43,44,45],asyncpg_deleg:41,asyncpgcompil:27,asyncpgcursor:27,asyncpgdbapi:27,asyncpgdialect:[3,27],asyncpgexecutioncontext:27,asyncpgiter:27,asyncpgjsonpathtyp:27,asyncpgsa:[14,43],asyncschemadropp:34,asyncschemagener:34,asyncschematypemixin:34,asyncvisitor:34,at:[2,3,4,7,8,10,12,14,15,21,23,29,38,39,41],ath:23,atom:23,attent:38,attribut:[3,10,12,14,23,24,29,33,41],attributeerror:21,audit_profil:11,aur:43,austin:10,auth_plugin:26,authent:4,author:[4,21,43,44],author_id:43,auto:[0,11,23,24],autocommit:[0,4,26],autogener:[6,44],autom:12,automat:[3,7,12,20,22,23,24,29,33,35,36,38],avail:[2,5,10,14,15,23,24,29,36,39,41],averchenkov:41,avoid:[3,4,33],awai:[2,4],await:[1,2,3,4,7,10,11,12,14,15,21,23,29,33,36,38,39,41,43,44,45],await_onli:3,awesom:[1,43],back:[2,3,4,12,15,21,29,36,41],backend:44,background:3,backport:[2,10,41],backward:41,bad:[3,41],bake:[7,19,21,22,41],baked_queri:[26,27,28],bakedqueri:[5,22],bakeri:[5,17,18,19,21,26,27],balanc:[4,23],barancsuk:41,bare:[4,5],base:[3,6,10,12,13,17,18,19,21,23,24,25,26,27,33,44],base_exp:32,basedbapi:[26,27,28],baseexcept:[15,36],basemodel:44,basic:[2,3,4,5,23,43],batch:[5,23],bayer:[4,43],be:[0,1,2,3,5,6,7,8,12,13,14,15,21,22,23,24,26,27,29,31,33,36,38,39,41],becaus:[2,3,4,7,10,14,15,21,23,36,41],becom:[2,3,14],been:[2,26,27,41],befor:[1,2,3,4,7,8,10,12,14,15,22,23,26,29,36,38,45],before_set:11,begin:[3,4,7,26,27,28],begun:3,behav:[23,41],behavior:[2,3,10,15,23,29,38,41],behind:[2,3,4,7,12,23,41],being:[2,3,4,14,23,41],belong:15,below:[8,10,11],benefici:4,besid:10,best:[8,41],beta:41,better:[4,41,43],between:[2,4,41],beyond:4,biginteg:[21,38,44],bin:44,binari:[28,41],bind:[2,4,7,10,14,15,21,22,23,26,27,34,41],bind_processor:[2,27],bindparam:7,bindtempl:27,binghan:41,birthdai:11,bit:[8,14,23,41],bite:[29,38],black:41,blob:[43,44],block:[2,3,4,15,29,36,38],blue:2,bondar:43,book:[10,21,43,45],booker:45,bookings_idx_booker_room:45,bookings_idx_day_room:45,bookings_pkei:45,bool:[11,44],booleanproperti:[11,32],boost:7,borrow:[2,15,29,38,39],boss:1,bot:43,both:[2,3,4,10,12,14,21,23,24,26,27,36,41,45],bottleneck:4,bound:[21,23,24,26,38],boundari:3,branch:8,bridg:3,brien:41,broken:41,browser:8,bryanforb:43,bsd:16,buffer:[3,4],bug:[3,8,10,41,43],bugfix:8,build:[3,4,8,10,12,21,23,44],builder:[12,44],built:[3,10,14,22,23,24,29,39,41],builtin:[38,41],bulk:[4,5,29],bunch:6,bundl:21,busi:[4,14],but:[1,2,3,4,7,10,12,14,15,19,21,22,23,24,29,33,41],by:[2,3,4,7,8,10,12,14,15,21,22,23,24,26,27,29,33,36,38,39,41,43],bypass:3,c10k:1,cach:[7,44],calcul:26,call:[2,3,4,7,10,12,15,20,21,23,24,26,27,29,33,36,41],call_next:10,callabl:[12,26,27,29,33,41],callableload:33,caller:10,came:4,can:[1,2,3,4,5,6,7,8,12,13,14,15,21,22,23,24,29,33,36,38,39,41],candid:41,cannot:[2,3,12,41],canopi:43,canopytax:43,cap:4,care:3,cast:[11,44],categori:12,categories_1:12,categories_2:12,caught:36,caus:[2,3,4,15,29,38,39,41,45],cd:[8,44],cdi:43,celeri:4,certain:[4,38],chain:[2,4,7,21,23,33,41],challeng:4,chanc:4,chang:[3,6,8,10,23,29,41,44],characterist:3,charset:26,chat:4,check:[2,3,8,10,12,14,23,26,27],checkfirst:[26,27,34],checkout:8,child:[12,41],child_id:12,children:[12,36],chmod:8,choic:[4,10,12],choos:[4,19,41],ci_:44,classic:12,classmethod:[7,23,28,33],claus:[2,10,12,14,21,23,26,27,28,29,33],clean:[10,41],cleaner:3,cleanli:[4,29],cleanup:39,clear:4,click:2,client:[4,8,43,44],client_flag:26,clone:8,close:[2,3,4,15,21,22,26,27,28,29,36,38,41,45],closer:3,cls:[7,11,24],cmd:44,cn:[8,43],code:[2,3,4,10,12,14,15,29,36,41,45],coin:4,col:12,collect:[23,44],collid:41,color:[2,26,27,28,29],colspec:[26,27],coltyp:[26,27],column:[2,5,6,7,11,12,14,21,23,24,26,27,29,33,38,41,43,44,45],column_kei:27,column_nam:23,columnattribut:24,columnload:[10,12,33],com:[8,10,16,43,44],combin:[2,12],come:14,command:[3,4,6],command_timeout:27,comment:[3,7,29],commit:[0,4,8,15,26,27,28,29,36,41,44],common:[7,14,15,29,39],commun:[3,43],compani:33,compar:[4,23,44],compat:[2,3,10,29,33,41],compil:[7,21,22,28,29],compiled_sql:22,complet:[2,10,21,41,44],complex:[5,7,12,43],compli:3,compliant:3,complic:0,compos:44,composit:23,concentr:4,concept:[2,14],conceptu:14,conclus:4,concret:[2,14,24,38],concurr:[3,4],condit:[12,23],config:[10,13,38,39,41,44],configur:[8,17,33,37,38],confirm:41,conflict:[3,12,33],conftest:44,confus:2,congratul:6,conn1:2,conn2:2,conn:[2,3,4,14,21,26,27,28,29,36,41],connect:[0,3,4,5,7,12,13,14,15,17,19,21,22,26,27,29,34,36,37,38,41,44],connect_timeout:26,connection_cl:29,connection_class:27,connectionless:2,conradi:41,consid:[3,4,10,41],consist:41,constantli:[3,7],constraint:[24,34,41],construct:[4,7,10,14,21,24],consum:[3,4],contain:[3,6,10,24],content:[17,18],context:[2,3,4,10,15,21,22,26,27,28,29,33,36,38,39,41,43,44],contextu:[10,12,22,38],contextualgino:10,contextvar:[2,10,17,20,43],continu:[15,36],contrast:4,contribut:[5,41],control:[3,5,26,27,29,38,41],conv:26,conveni:[2,3,4,14,15,21,23,43],convers:[2,26,27],convert:33,cool:1,cooper:4,cooperative_multitask:1,copi:[3,10,29,41,44],core:[2,3,5,7,10,16,21,22,24,41,43,45],coroutin:[1,2,3,4,23,29,35,41],correctli:[3,4,14,15,21,23,41],correspond:[2,12,22,23,24,26],correspondingli:[2,15,36],could:[2,3,4,7,10,12,22,23],count:[2,10,12,45],count_1:45,cov:44,cover:41,coverag:41,cpu:1,cpython:43,creat:[0,3,4,5,7,8,10,12,14,15,19,21,22,23,24,26,27,29,33,34,35,36,38,39,41,43,44,45],create_al:[3,7,10,11,12,14,21,34,41,45],create_async:[26,27,34],create_async_engin:3,create_engin:[2,3,4,7,10,13,14,19,21,26,29,33,35,41,45],create_ok:34,create_pool:[2,41],create_t:44,create_task:10,createdb:[44,45],creation:[2,7,10,21,22,41],credenti:6,credit:8,cross:38,crt:8,crucial:15,crud:[2,4,10,12,14,17,18,19,21,29,41,43],crudmodel:[21,23,41],csrf:44,ctrl:44,ctx:12,cur:44,curatedlist:43,current:[2,3,10,13,15,19,23,26,27,29,41],current_connect:[0,29,41],current_databas:10,current_us:[4,43],cursor:[2,3,26,27,28,29],cursor_cl:[26,27,28],cursorclass:26,custom:[3,5,11,12,23,24,29,41],customiz:[24,41],cut:4,cutil:41,cve:44,dahlia:43,dai:45,daisi:[11,24,43,45],damn:1,danger:38,darwin:44,data:[2,3,10,11,12,23],databas:[0,2,3,5,6,7,8,11,12,14,15,19,21,23,24,26,27,29,33,36,38,39,41,43,44],datastructur:44,date:45,datetim:[10,11,12,24,33],datetimeproperti:[11,32],db:[0,2,4,5,7,8,10,11,12,13,14,15,21,23,24,26,27,29,34,36,38,39,41,43,44,45],db_age:23,db_databas:[38,44],db_driver:44,db_dsn:44,db_echo:[10,38,41,44],db_host:[13,38,44],db_kwarg:[13,38],db_name:[6,8],db_pass:8,db_password:[38,44],db_pool_max_s:[38,44],db_pool_min_s:[38,44],db_port:[8,38,44],db_retry_interv:44,db_retry_limit:44,db_ssl:[38,44],db_time:7,db_use_connection_for_request:[38,44],db_user:[8,38,44],dbapi:[26,27,28],dbapi_class:[26,27,28],dbapi_conn:[3,26,27],dbapicursor:[26,27,28],dbname:[10,44],ddl:[34,44],dead:4,deadlock:[3,4],deal:[4,10,12,15],debug:38,decent:3,decid:3,decim:41,declar:[12,14,17,18,19,21,23],declarative_bas:24,declared_attr:[7,11,24,41,45],decod:32,decor:[7,24],deeper:3,def:[1,2,3,4,7,10,11,12,14,24,26,27,38,44,45],default_isolation_level:26,defaultdialect:[26,27],defer:4,defin:[3,5,6,7,11,12,13,14,21,24,29,33],definit:[10,12,24],del:10,delai:4,deleg:[2,14,21,23,41],delet:[12,14,23,41,44,45],delete_us:44,deliv:3,demand:7,demo:44,depend:[2,4,6,7,10,23,29,36,41,44],deploi:4,deprec:[3,33,41],describ:[4,10],descript:[6,8,26,27,28,39,44],design:[2,3,4,10],detail:[2,8,10,12],detect:44,determin:[4,12],deutel:41,dev:[43,44],develop:[6,8,17,38],diagram:2,dialect:[2,3,10,11,13,17,18,19,22,29,34,36,39,41],dict:[2,10,11,13,23,24,33,41,44],dictionari:[2,29,38],did:41,didn:[2,4,31],differ:[2,3,4,5,11,12,14,21,23,24,29,41],dig:14,direct:[4,26,29],directli:[2,3,4,7,10,12,14,21,23,24,26,29,38,41],directori:[6,41],dirti:[10,41],disabl:[23,41],disable_inherit:41,disable_task_loc:41,disadvantag:4,disallow:10,disast:[15,38],discard:[2,23,29],discord:43,discourag:4,discuss:10,disproportion:4,dist:41,distinct:[12,23,33,41],distinguish:2,divio:16,django:5,do_load:33,do_on_connect:[26,27],doc:[3,6,8,10,15,41,43,44],docker:[8,44],dockerfil:44,docstr:8,document:[2,3,6,7,10,41,43],doe:[2,3,4,5,12,14,21,23,29,33,41,43],doesn:[2,3,4,10,11,12,24,41],doge:3,doing:[4,7,21,41],don:[0,2,3,5,10,12,15,23,29],done:[0,1,2,6,8,10,14,15,22,41,44],doubt:12,down:[4,44],downgrad:[6,44],driven:8,driver:[2,3,10,15,26,27,29,39,41],drivernam:44,drop:[34,41],drop_al:[3,10,12,34],drop_async:[26,27,34],drop_ok:34,drop_tabl:44,dsn:[39,41,44],due:[4,41],dure:[2,22,24,29,38,41],dynam:[12,14,24],dziewulski:41,each:[2,3,4,6,7,10,12,22,24,29,33,38],earli:[3,15,17,36,39],earlier:[3,10,39],easi:[2,3,10,12],easier:[3,7,10],easili:[2,3,4,12],echo:[2,3,10,29,39,41,44],ecosystem:[3,4],edit:11,effect:[19,23,29],effici:2,effort:4,either:[2,3,4,6,7,10,14,15,23,29,36],elem:[21,22,28],element:21,els:[2,3,4,10,14,15,29,38,44],email:[4,10],email_address:14,emerg:41,emit:3,empti:[2,38,39,41],en:[1,43],enabl:[2,8,10,23,24,33,39,41],enable_inherit:41,enable_task_loc:41,encapsul:[3,4,10],encod:[32,43],encourag:21,encrypt:8,end:[2,3,4,12,15],endpoint:4,enforc:[3,12,15],engin:[0,3,5,15,17,18,19,21,22,23,26,35,36,39,43,44,45],engine_cl:35,engine_from_config:21,enginestrategi:35,enhanc:[4,41],enjoi:38,enough:4,ensur:41,enter:[15,21,29],entri:[14,31,44],entry_point:44,enumer:26,env:[6,10,44],environ:[8,41,44],ep:44,equal:[2,23,41],equival:[23,24,26],error:[3,10,27,28,41],es:24,especi:[2,4,10,12,41],establish:38,etc:[3,7,26,27],even:[2,3,4,7,10,14,21,23,24,29,38,41],event:[1,3,4,8,26,27,41,43],eventlet:43,eventu:[2,3,10],ever:[3,36],everi:[7,8,12,23],everyon:2,everyth:[2,3,4,10,14,15,38],evil:3,exactli:[2,12,21,29],exampl:[2,3,4,6,7,8,10,12,13,14,15,21,23,29,33,36,38,41,45],except:[1,2,3,10,15,17,18,19,27,28,29,36,38,39,41],exchangeratesapi:43,excit:3,exec:8,execut:[0,3,4,5,7,11,12,14,21,22,23,26,27,28,29,33,39,41,45],execute_bak:[26,27,28],executemani:[28,29,41],execution_ctx_cl:[26,27],execution_opt:[2,3,7,10,12,21,22,23,26,29,33,41],executioncontextoverrid:[26,27,28],exhaust:[2,4],exist:[2,3,4,5,21,23,24,26,27,39,41],exit:[15,21,29,36],exp:11,expect:[3,33],experiment:[12,23,33],explain:[3,4,10,12],explan:43,explicit:[0,2,3,10,12,15,26,41,43],explicitli:[2,3,4,7,10,11,36,38],explict:14,expos:[14,21,41],exposur:3,express:[5,11,23,29,33],ext:[3,10,13,17,18,19,21,38,39,41,44],extens:[2,7,10,13,14,21,31,38,39,41],extra:[3,33,41,44],extract:41,extrem:2,f2c273a43a3ed893a767d4239046f2befabf510d:43,face:4,facebook:43,facil:26,fact:[3,10,26,27],factori:[22,24,39],fail:[2,21,41,44],fals:[2,3,4,7,11,14,23,24,26,27,28,29,34,38,39,41,44],fantix:[23,43,44,45],far:[3,14],fast:[4,38,43],fastapi:[4,10,42,43],faster:[7,22],featur:[2,3,5,8,12,23,33,41],fed:[7,12,41],feed:[29,33],feedback:8,feel:[3,23,29],fetch:29,fetchal:3,fetchval:3,few:[2,4,10,29,38,41],field:[10,11,14,23],file:[1,6,8,41,44],fill:33,filter:23,find:[3,4,6,24,31],fine:[3,4,10,14],finish:[2,4,6,23,29,38,44],first:[2,3,4,5,7,8,10,12,14,19,21,23,26,27,28,29,33,38,39,41,45],first_connect:[26,27],first_nam:10,first_or_404:41,firstli:12,fit:2,five:41,fix:[3,4,10,41],fixtur:44,flag:[26,27,39],flake8:8,flat:10,flexibl:[11,12,43],flow:4,flush:4,fly:5,focu:10,folder:6,follow:[2,3,4,10,11,15,21,33,36],forc:4,foreign:[10,12,23],foreignkei:[10,12,14,43],forev:[3,23,29],forget:[2,3],fork:8,format:[10,26,38],former:2,fortun:4,forward:[12,26,27,28],found:[2,4,10,12,21,22,24,41,45],foundat:[4,43],founder:45,founding_us:45,fouser:21,framework:[7,10,41],free:[4,23,29],freebsd:43,freez:4,frequent:12,friendli:41,from:[2,3,4,6,7,10,11,12,13,14,15,21,22,23,24,29,33,36,38,39,41,43,44,45],fromclaus:33,frustrat:3,full:[2,6,10],full_path:6,fulli:29,fullnam:14,fun:4,func:[10,12,33,45],func_or_elem:[21,22],fundament:[3,4],further:[2,3,7,14,29,45],furthermor:[2,7,29,38],fut:10,futur:[3,10,12],gain:12,galden:41,garciasilva:43,gbasic:43,gcc:44,gener:[2,11,12,23,24,26,27,33,44],geoalchemi:43,get:[2,3,4,5,7,10,12,14,19,21,22,23,24,29,33,38,41,43,44,45],get_affected_row:[26,28],get_app:44,get_column:33,get_current_connect:41,get_event_loop:45,get_from:33,get_isolation_level:[26,27],get_lastrowid:[26,28],get_loc:41,get_now:2,get_or_404:[38,41,44],get_profil:32,get_raw_connect:29,get_result_proxi:28,get_statusmsg:[26,27,28],get_us:[38,44],get_vers:19,getattr:44,getlogg:44,getter:[7,21],gevent:43,gino:[2,3,4,5,6,8,11,12,13,15,17,18,38,39,42],gino_db:8,gino_fastapi_demo:44,gino_fastapi_demo_test:44,gino_starlett:31,ginoconnect:[2,14,15,17,22,29,36],ginoengin:[2,10,14,15,21,22,23,29,33,35,36,41],ginoexcept:30,ginoexecutor:[2,7,21,22],ginonulltyp:[26,27],ginopool:41,ginoschemavisitor:[14,21,34],ginostrategi:35,ginotransact:[15,29,36,41],git:[8,29,44],github:[8,10,16,43,44],gitignor:44,gitlab:43,gitter:16,give:[1,3,4,12,22,33],given:[2,3,8,10,12,21,22,23,24,26,27,29,33,35],global:[7,14,21],gmail:44,gnu:43,go:[1,6,14],goe:45,going:3,golden:4,goncharov:41,gone:41,good:[1,3,29],got:1,gotta:1,grab:3,grai:2,grammar:[4,10,41],grandson:12,grant:3,great:[4,6,10],greater:20,greatli:[2,3,8],green:2,greenlet:[3,10],greenlet_spawn:3,group_bi:[10,12],guarante:[4,15,29],guess:4,guess_model:41,guidelin:5,gunicorn:44,hack:10,had:[14,24],hand:2,handl:[2,4,15,29,36,41],handler:38,hang:[3,4],happen:[3,4,21,29],hard:4,hardwar:4,harm:4,has:[2,3,4,7,10,12,14,15,19,21,22,23,26,27,29,41],has_schema:27,has_sequ:27,has_tabl:[26,27],has_typ:27,hash_:22,haunt:[3,4],have:[1,2,3,4,6,7,10,12,14,15,23,33,36,39,43],head:[2,6,44],heavi:3,hei:[1,4],height:11,help:[0,8,15],helper:7,henc:21,here:[1,2,3,4,7,8,10,11,12,14,15,23,29,36,41,44,45],herebi:21,hidden:[2,3],hide:[2,41],hierarch:[10,12],high:[4,7],hit:4,hmm:2,hold:[1,3,10,15],holubakha:41,hong:43,hood:[2,23],hook:[5,10,21,26,27,31],hoorai:3,host:[26,27,39,44],how:[0,2,3,5,6,8,12,15,29,43],howev:[2,3,4,10,14,21,23,29,41],html:43,http:[1,6,8,10,16,29,43,44],hurri:1,hybrid:4,id:[6,7,10,11,12,14,21,23,24,26,33,38,43,44,45],id_val:10,ide:43,idea:[3,4],ident:[2,3,12,14,23,41],identifi:4,idl:[3,4],ids:12,ignor:22,ila:41,im:16,imagin:3,immedi:[2,4,7,19,23,29,36],immut:2,impl:44,implement:[2,3,24,26,33],implic:14,implicit:[0,3,4,10,15,21,26,41,43],implicitli:[2,4,7,10,14,15,29,36],importantli:4,importlib:44,imposs:4,improv:41,in_:12,in_queri:23,inc:43,includ:[3,8,10,23,39,41],include_foreign_key_constraint:34,include_rout:44,increment:23,index:[5,12,24,34,43,45],index_on_nam:10,indic:[24,33],individu:[2,3,23],infer:23,info:44,inform:[2,7,10,14,15,21,22,29],inherit:[2,7,10,15,22,23,29,41],ini:[6,44],init:[6,26,27,41,44],init_app:[13,38,39,44],init_command:26,init_kwarg:[26,27],init_pool:[26,27,28],initi:[2,3,5,7,11,14,23,24,26,27,33,35,39],initializederror:30,inject:[2,14],inlin:[14,21,27,41],inner:[2,15,26,27],ins:14,insert:[3,5,14,15,23,26,29,41,43,45],insid:6,insist:12,inspir:[12,14],instal:[6,8,39,41,44,45],instanc:[2,4,6,7,10,11,12,14,15,21,22,23,24,26,27,29,32,33,35,41],instant:23,instanti:[14,23,29,33],instead:[2,3,4,12,14,15,23,24,29,31,41],integ:[6,7,10,11,12,14,24,43,45],integerproperti:[11,32],integr:[5,38,41],intend:24,intens:4,interest:3,interfac:[2,3,5,21,33,41],interfaceerror:27,interfer:41,interim:3,intern:[2,10,12,15,22,23,24,36,41],internet:4,interpret:[12,36],interv:27,into:[2,3,4,7,8,10,11,12,14,15,22,23,26,27,29,31,33,41,45],introduc:[3,10,12,41],intuit:4,invalid:38,invent:10,invert_get:24,invertdict:24,invok:[23,26,27],involv:[10,14,26],io:[1,29,43],irrelev:21,is:[1,2,3,4,5,6,7,8,11,12,14,15,16,20,21,22,23,24,26,27,29,33,35,36,38,39,40,41,43],is_:23,is_local_root:41,isdigit:38,ish:4,isol:[0,2,26,27,29,41],isolation_level:[2,3,26],issu:[3,4,8,10,11,26,41,43],it:[1,2,3,4,5,6,7,8,12,14,15,21,22,23,24,26,27,29,33,35,38,41,43],item:[12,14,33,34,44],iter:[2,3,10,12,14,21,23,24,26,27,28,29,41,43],its:[2,3,4,10,12,21,23,24,26,27,29,33,36,39,41,45],itself:[2,3,4,10,12,14,15,24,26,27],iuliia:41,jack:14,java:43,jeff:10,jekel:41,jetbrain:43,jim:41,job:29,join:[5,12,21,23,33,41,43],join_queri:12,join_without_n_plus_1:4,jone:[14,43],json:[5,23,26,27,38,41,44],json_support:[17,18,19,23],jsonb:[11,41],jsonindextyp:26,jsonpathtyp:[26,27,41],jsonproperti:[11,23,32],julio:41,just:[1,2,3,4,6,10,12,14,15,23,24,36,38,41],keep:[3,4,10,12,26,41],kei:[8,10,12,23,24,26,33,41],kentoseth:41,kept:[4,33],keyout:8,keyword:[2,3,7,10,12,19,22,23,33],kill:[1,4],kind:4,king:[43,44],kinwar:41,know:[1,2,3,4,10,15],knowledg:[12,14,23],known:[2,12],ko:41,kooten:41,kovalev:41,kubernet:44,kw:[12,26,27,34],kwarg:[19,21,22,23,24,26,27,28,29,34,35,36,39,41],label:[33,41],lacerda:41,lambda:12,larg:[3,4,29],last:[2,4,12,21,23,36],last_nam:10,lastli:3,lastrowid:26,later:[1,2,10,12,14,29,39],latest:[23,29,43],latter:2,law:4,layer:4,layout:41,lazi:[0,3,10,17,29,37,38,41],lazili:[2,7,19,38],lazy_engin:10,lead:[3,4,11],leaf:12,learn:[2,4,22],least:[4,7],leav:[3,29],led:29,left:[2,3,12,23,43],legaci:14,len:12,length:14,lengthi:4,leosussan:43,less:[2,4,14],let:[1,2,3,4,7,12,14,15],level:[0,2,4,7,10,11,14,23,24,26,27,29],leverag:[3,4,11],li:41,lib:[8,44],libffi:44,librari:[3,41,43],licens:43,liebig:4,lifetim:38,lightweight:6,like:[2,3,4,6,7,8,10,11,12,14,23,24,29,31,38,39],likewis:[2,12,33],limit:[3,4,26,27,28,41],line:[2,6],link:[3,4,6,10],list:[2,8,10,11,14,29,33],listen:[3,4,26,27],liter:29,littl:8,ll:[1,2,4,10,11,12,29,41],load:[2,3,4,5,7,12,21,23,29,33,41,43,44],load_modul:44,loader:[5,10,17,18,19,21,23,28,29,41,43],lobbi:16,local:[2,6,8,17],local_infil:26,localhost:[3,4,7,8,10,12,13,14,33,38,39,44,45],locat:[23,41],lock:[3,4,44],log:44,logger:44,logging_nam:[2,29],logic:[3,4],login:8,longer:[2,4,10,29,41],look:[1,4,12,23,39],lookup:[23,41],loop:[3,10,21,26,27,28,29,35,41,43],lose:29,lost:4,lot:[3,4,12],love:4,low:[4,7],lower:[3,10],luckili:3,made:[3,10,12,14,41],magic:[2,3,7,12],magicstack:[10,43],mai:[2,3,4,10,11,12,15,21,22,26,29,38],main:[2,3,4,6,7,10,12,14,44,45],main_app:6,maintain:[3,4,10,24,43],mainten:[3,41],major:4,make:[2,3,4,6,7,8,10,12,23,26,27,29,38,44],make_express:32,make_url:44,manag:[0,1,3,7,15,21,29,36,38,41],mandatori:14,mani:[2,3,4,5,10,26,27,28,29,41],manipul:41,manual:[3,5,10,11,12,14,23,29,36,41],map:[2,10,12,14,43],mapper:10,marissa:10,mark:[4,24,29],martin:41,masonri:44,massiv:23,master:[43,44],match:[3,12],matchtyp:26,matter:[2,3,12,14],max:[2,44],max_cacheable_statement_s:27,max_cached_statement_lifetim:27,max_inactive_connection_lifetim:27,max_overflow:4,max_queri:27,max_siz:27,maximum:39,maxsiz:26,mayb:3,me:[1,2,3,4],mean:[2,4,12,15,23,24,26,27,29,41],meaningless:[2,24],meant:2,meanwhil:[7,23,36,41],meet:8,member:45,memori:[2,10,23,24,29],mention:[2,3,14],mess:21,messag:[4,41],meta:3,meta_path:31,metaclass:23,metadata:[2,7,10,14,21,22,23,24,34,41,43,44,45],method:[2,3,4,7,14,21,22,23,24,26,27,29,33,36,41],micha:41,michael:43,middl:39,middlewar:[10,39,41],might:[2,3],migrat:[5,17,44],mike:4,mimic:3,min:44,min_siz:[2,27],minhe:43,minim:14,minimum:4,minsiz:26,misc:41,miser:4,miss:[2,3,41],mission:4,mistak:41,mix:[3,4],mixin:[24,41,45],mkdir:44,mkvirtualenv:8,mock:33,mod:44,mode:[2,3,4,26,27,29,36,39],model:[2,4,5,6,7,10,11,14,21,23,24,28,29,33,38,41,42,43,45],model_base_class:21,model_class:[21,24],model_kei:10,modelload:[10,12,33,41],modern:4,modif:45,modifi:[3,12],modul:[2,6,10,17,18,41,44],modulenotfounderror:6,moment:[1,2,4],more:[2,3,4,7,10,12,14,15,21,22,23,24,29,41],morgan:41,most:[2,3,4,10,14,15,23,26,29,41],mostli:[3,4,23],move:[3,41],much:[2,3,4,11,14,15,29],multiparam:[2,21,28,29,41],multipl:[2,5,12,21,22,29,33,41],multipleresultsfound:[29,30],multitask:4,musl:44,must:[2,3,4,6,10,14,21,29,33],mutabl:41,my_app:6,myapp:44,mydb:44,mydb_test:44,mydialect:[26,27],mykyta:41,mymodel:44,myself:[1,4],mysql:[10,26,41,43,45],mysqlcompil:26,mysqldialect:26,mysqlexecutioncontext:26,mytab:15,mytabl:15,myuser:23,name:[2,3,4,6,7,8,10,11,12,14,21,23,24,33,35,38,39,41,43,44,45],name_or_url:35,namespac:31,nativ:[3,11,41],natur:4,neal:41,neat:4,neath:3,necessari:[4,26,27,38],necessarili:2,need:[1,2,3,4,6,7,8,10,11,12,14,15,19,21,29,38,39,41],neither:22,nest:[2,3,5,12,29,33,36],network:[2,4],never:[4,14,15,29],nevertheless:4,new_child:12,new_nam:10,new_names_dict:10,newer:3,newli:[23,26,27,41],next:[3,4,6,14,26,27,28,29],nicknam:[6,14,38,44,45],no:[1,2,3,4,6,7,10,12,14,15,19,20,21,23,24,26,27,29,36,41,44],no_delai:26,no_deleg:21,non:[10,13,14,29,41],nonam:[6,14,45],none:[2,3,10,12,14,21,22,23,24,26,27,28,29,32,33,34,35,38,39,41,44,45],none_as_non:[17,23,33],nor:22,noresultfound:[29,30],normal:[2,3,4,7,11,12,14,15,21,23,24,36],nosuchrowerror:30,not:[1,2,3,4,5,12,14,15,16,21,23,24,26,27,29,33,36,38,41,43],note:[2,3,12,19,26,29,38,41],noth:[1,2,3,4,10,15,33,41],notic:[2,12],now:[1,2,3,4,6,7,8,10,12,14,15,21,29,33,41],nullabl:[11,14,44],nullpool:[13,27,41],nulltyp:[26,27],number:[2,4,11,23,39],numer:[26,28],obj:34,object:[2,3,4,6,7,10,11,12,14,21,22,23,24,26,27,28,29,32,33,34,36,41,43,44],objectproperti:[11,32],obviou:4,obvious:[4,41],occasion:[4,21],occur:[26,27],odd:3,of:[1,2,3,4,5,6,7,11,14,15,21,22,23,24,26,27,28,29,31,33,35,36,38,39,41,43],off:[2,3,4,38,41],offer:[2,14,36,41],offici:[6,21,31],often:[21,23,24],oh:1,ok:3,okai:1,olaf:41,old:[3,23,41,43],olexii:41,omit:[12,23],on:[1,2,3,4,5,8,12,14,15,21,22,23,24,26,29,33,36,38,39,41,43,44],on_claus:[23,33],on_connect:[26,27],onc:[2,4,6,10,12,22,24,26,27,29],one:[1,2,3,4,5,6,7,10,14,21,23,26,27,28,29,33,41],one_or_non:[2,7,14,21,29,41],ones:[2,3,4,21,33],onli:[2,3,4,6,7,12,13,14,15,21,22,23,24,26,29,33,36,39],ons:2,op:[3,20,44],open:[3,6,8,15,29,43],openid:4,opensourc:43,openssl:[8,44],oper:[2,4,21,23,24,29,41,43],opposit:[4,29],ops:3,opt:29,optim:2,option:[2,3,7,10,12,13,21,22,23,26,27,29,33,41],or:[2,3,4,5,6,7,8,12,14,15,20,21,22,23,24,26,27,29,33,35,36,38,39,41,44],order:[2,3,10,12,23,29],ordinari:12,org:[1,6,43],origin:[3,8,14,23],orm:[0,2,3,5,12,16,43,45],orphan:2,orz:43,os:[1,3,4],oss:43,other:[2,3,4,5,6,8,13,14,15,21,23,24,29,33,39,41],otherwis:[24,26,27,29],our:[4,6],out:[1,2,4,8,14,24,29],outer:[2,12,15,23],outerjoin:[10,12,33,41,43],output:[3,12,14],over:4,overal:[4,12],overhead:4,overload:4,overrid:[7,21,23,24,41],overwrit:3,own:[2,4,10,12,13,14],owner:8,packag:[6,17,18,39,41,43],pai:38,pair:[12,23],parallel:2,param:[21,28,29],paramet:[2,5,7,14,24,27,28,29,33,39,41,45],paramref:26,paramstyl:[2,26,28],parent:[2,10,12,14,21,36,41],parent_id:[10,12,41],parents_x_children:12,parentxchild:12,pars:[29,41],part:[2,3,4,10,26],partial:[2,10,41],particular:[26,27],particularli:23,pascal:41,pass:[7,8,26,27,29,33,39,41,44],passfil:27,passin:8,passiv:41,passout:8,password:[6,8,26,27,39,44],patch:[10,20,41],patch_asyncio:20,patch_schema:34,pattern:[4,10,12],pavol:41,payload:2,pem:8,pend:[3,17,23],peopl:[3,4,12],pep:[3,41,43],per:[3,26,27,29],perform:[4,7,10,38],perman:[2,3,29,39,41],persist:3,person:3,peter:43,pg:[41,44],pgcompil:27,pgdialect:[3,27],pgexecutioncontext:27,pgjone:43,philip:43,pictur:3,piec:12,pip:[6,8,39,41,44,45],place:[3,24,26,29,41],plai:[2,4,21,29],plain:[2,10,43],plain_old_java_object:43,platform:[4,44],pleas:[1,2,3,7,10,11,12,14,15,19,21,29,38,41],plu:[7,21],pluggi:44,plugin:44,poetri:[6,41,44,45],point:[3,4,29,31,44],poli:24,pool:[2,3,4,7,13,22,26,27,28,29,38,39,41,44],pool_class:[13,26,27,28],pool_max_s:[39,44],pool_min_s:[39,44],pool_recycl:26,poolev:[26,27],pop_bind:[2,21,41,45],popo:43,popul:[23,45],port:[3,4,14,26,27,39,44],posit:[7,23,26,27,29,33],possibl:[2,3,4,6,7,12,14,22,26,27,33,38,41],post:[2,3,12,44],post_exec:26,postfetch_lastrowid:26,postgi:43,postgr:[6,8,10,35,38,39,44],postgreserror:27,postgresql:[2,3,4,7,8,10,11,12,13,14,21,27,33,35,36,41,43,44,45],postgresqlimpl:44,postprocess:29,potenti:[3,12],pr:[43,44],practic:[3,4,29],pre:[3,19,22],prebak:[7,19,26,27],preexecute_autoincrement_sequ:26,prefer:[4,26,33,38],prefetch:41,prefix:35,prepar:[5,6,19,22,26,27,28,29,41],preparedstat:[27,28],present:[2,12,29,41],press:44,pretti:[4,14],prevent:[7,41],previou:[2,12,14,21,39,41],previous:[2,12],primari:[23,26,41],primary_kei:[6,7,10,11,12,14,21,24,38,43,44,45],primarykeyconstraint:[44,45],primit:3,princip:[3,4],print:[3,5,7,11,12,14,23,43,45],prioriti:4,privkei:8,probabl:[2,3],problem:[3,4,6,41],proce:[26,27],process:[2,12,26,27,41,44],process_row:28,processor:[12,41],procur:26,produc:[2,12],product:0,profil:[11,32,41],program:[2,3,4,12,15],program_nam:26,progress:2,prohibit:15,project:[6,8,10,44],promis:12,prop_nam:[11,24,32],propag:15,proper:[3,4,22],properti:[2,4,5,6,10,12,14,21,22,26,27,28,29,33,36,41],protect:41,provid:[2,3,7,10,11,12,14,15,21,22,23,29,31,33,39,41],proxi:[23,26],psql:8,psycopg2:[2,3,44],psycopg:44,publicli:[21,29],pull:5,pure:[10,41],push:8,put:[2,3,8,14,36],pwd:8,py:[6,8,10,41,43,44],pycharm:43,pydant:44,pypi:[16,41],pyproject:44,pytest:[8,44],python:[1,2,3,4,6,8,10,11,16,20,24,31,41,42,44,45],pythongino:43,pythonpath:[6,44],qbasic:43,qualiti:4,quart:[41,43],queri:[0,4,5,7,8,11,12,14,17,19,21,22,23,24,26,27,28,29,33,39,43,45],query_cl:22,query_executor:21,query_ext:21,query_param:10,querymodel:23,question:[3,4],queue:[3,4,22],quick:5,quickli:[3,10],quit:[2,3,4,10,14,23,44],qulaz:41,rais:[2,10,15,21,29,36,41],raise_commit:[15,36],raise_for_statu:44,raise_rollback:[15,36],raiseerr:44,ram:43,randint:[10,12],random:[10,12],rang:[10,12,41],rather:[4,10,23],raw:[2,4,5,12,13,14,22,29,33,41],raw_conn:[26,27,28],raw_connect:[29,41],raw_pool:[26,27,28,29,41],raw_transact:[15,26,27,28,36],rdbm:[38,45],re:[2,3,4,7,8,10,12,23,29,41],reach:[36,38],reaction:4,read:[2,3,10,21,22,23,24,29],read_commit:3,read_default_fil:26,read_default_group:26,read_root:4,readi:8,readm:[8,41],realiti:4,realli:1,reason:[3,4,26,27],receiv:[3,10,26,27],recent:[2,29],recogn:[3,15,23,41],recommend:[2,21,29,38],reconsid:4,record:[3,41],record_class:27,recov:41,recurs:[12,29],recv:1,redirect:24,reduc:[12,14],refactor:41,refer:[2,10,12,15,21,29],referenc:[5,29,36,41],reflect:24,refresh:41,regardless:3,regular:[3,12,14],reinvent:4,rel:6,relat:[2,10,12,43],relationship:[4,5,23,29,41],releas:[2,10,17,26,27,28,29,38,39],relev:[21,29,41],reli:4,reliabl:[3,4],reload:[23,32,44],remain:[10,41],rememb:[4,8,23],remind:4,remov:[2,23,41],renam:41,replac:[2,3,24,33,39,41],repli:4,repo:8,report:8,repr:[26,27,28,29],repres:[10,21,22,29,36],represent:41,req:8,requeijo:41,request:[4,5,10,23,38,39,44],requir:[2,3,4,8,14,21,23,24,29,41,44],requirements_dev:8,reserv:3,reset:[3,41],reset_loc:41,reskov:41,resourc:[2,4],respond:4,respons:[12,29,38,39],rest:[11,23,36,38],restor:3,restrict:21,result:[2,3,5,12,21,22,24,26,27,29,33,41],result_processor:[2,26,27],resultproxi:26,resum:[4,21],retriev:[2,12,23,44],retry_interv:44,retry_limit:44,return_model:[2,21,23,28,29],reus:[0,4,12,15,29,33,38,41],reusabl:[0,29],revamp:41,reveal:3,revers:[2,14,29],revert:[2,41],review:[3,41],revis:[5,11,44],rewritten:[14,41],rewrot:41,rez:43,ricardo:43,rich:[12,41],right:[0,3],risk:[2,3,14],riski:3,rm:8,ro:8,roald:41,role:8,roll:[3,15,29,36],rollback:[3,4,15,26,27,28,29,36,41],roman:41,room:45,root:[4,29],rootdir:44,rout:38,router:44,row:[2,3,7,10,12,14,23,26,27,28,29,33,41],rowproxi:[2,14,33,41],rsa:8,rst:[8,44],rule:[2,14,15,29,33],run:[1,2,3,4,5,6,7,8,11,12,29,33,38,39,44],run_sync:3,run_until_complet:45,runtim:[7,44],rv:44,sa:[3,7,44],sa_conn:29,sacrific:4,safe:[14,29],safe_connect:3,sai:[3,4,38],said:4,same:[2,3,4,5,6,7,11,12,14,15,21,23,24,29,33,38,39,41],sampl:[3,6],sanic:[13,17,37,41,43],sanicframework:43,saniti:3,save:[2,29,32],savepoint:[15,36],scalabl:4,scalar:[2,3,4,7,10,14,21,23,28,29,41,45],scenario:[2,4,7,12,14],scene:[2,3,7],schema:[6,14,17,18,19,21,24,26,27,29,43],schema_ext:21,schema_for_object:29,schema_visitor:21,schemadropp:34,schemagener:34,schemaitem:[21,41],scope:[4,38],scott:3,sebasti:43,sec:1,second:[2,3,4,12,23,29],secret:44,section:8,secur:10,see:[1,2,3,6,7,8,10,14,15,19,29],seek:4,seem:[1,14],seen:[3,7],select:[2,3,4,7,10,11,12,14,15,21,23,26,29,33,41,43,45],select_from:[10,12,33],self:[5,10,11,21,23,26,27,29,33,38],send:[3,4,8,27],sens:4,sent:21,sep:12,separ:[4,8,12,15,41],sequenc:[2,26,27,34],sequence_nam:27,sergei:41,seri:[2,29],serializ:3,serv:4,server:[2,4,8,10,29,38,39,41,44],server_default:[10,11,12],server_public_kei:26,server_set:27,servic:43,session:[3,4,44],sessionmak:4,set:[2,3,5,7,8,10,12,14,21,22,23,24,26,27,29,33,38,39,41,45],set_bind:[2,7,10,21,22,29,41,45],set_except:10,set_isol:26,set_isolation_level:[3,26,27],set_main_opt:44,set_result:10,setattr:[12,33,41],setter:[3,10,12,21],settl:38,setup:[6,27,39],sever:[2,21,23,29],shadow:22,shall:[15,23,38],shallow:3,share:[2,7,29,38,39,41],shortcut:[2,4,12,15,19,21,22,23,29,33,41],shortest:4,should:[2,3,4,8,12,13,21,23,24,26,27,29,31,39,41],shouldn:[4,41],show:[3,12],shown:[2,36],shtrikker:41,shut:44,shutdown:44,side:[2,4,12,29],sign:1,signific:3,silenc:1,simeon:41,similar:[2,3,6,7,8,12,14,15,21,23,41],similarli:[2,12,14,15,23,29,41],simpl:[2,4,6,10,14,21,41,43],simpler:2,simplest:33,simpli:[2,3,4,10,11,12,21,23,29,33,38,39],simplif:3,simplifi:7,simul:[2,3,10],sinc:[1,41],singl:[2,3,4,12,14,23,26,27,29],situat:[4,29,36],skip:[15,36],sleep:[1,2,4],slower:4,small:2,smaller:4,smarter:4,so:[2,3,4,7,8,10,12,14,15,21,23,24,26,27,29,31,36,41],soft:3,softwar:[3,43],sole:[26,27],solut:[0,4,31],solv:[3,4],some:[2,3,4,6,7,8,10,11,12,14,23,29,39,41],someth:[2,4,12],sometim:[2,3,4,38],soon:38,sourc:[23,29,41,43],speak:[2,3],special:[12,26,27,38],specif:[2,23],specifi:[2,11,12,14,15,23,24,26,27,29,33,39,41],sql:[2,3,4,5,11,12,14,21,22,23,26,27,29,34,41,43,45],sql_mode:26,sqlalchemi:[0,2,4,5,6,7,11,12,14,16,19,21,22,23,24,26,27,29,33,34,35,39,41,43,44,45],sqltype:[26,27],src:[41,43,44],ssl:[5,8,26,27,39,41,44],ssl_cert_fil:8,ssl_key_fil:8,stabil:10,stabl:[3,41,43],stack:[2,29,41],standard:[2,3,4],starleet:41,starlett:[10,17,31,37,41,43,44],start:[2,3,4,5,7,10,15,23,29,36,38,41,44],startup:44,starv:0,starvat:4,state:4,stateless:[4,10],statement:[2,3,4,5,10,14,15,19,22,23,26,27,28,41],statement_cache_s:27,statement_compil:[26,27],statu:[2,3,7,14,15,21,23,28,29,41,45],status_cod:44,stave:4,step:[2,3,4,7],stereotyp:4,still:[2,3,4,10,12,14,22,24,29,33,36,41],stop:[4,15,38],storag:[24,41],store:[1,2,3,11,12,24,26],stori:3,storm:[3,41],str:[4,11,22,33,44],straightforward:4,strategi:[2,17,18,19,33],string:[2,7,10,11,12,14,21,22,23,24,26,27,29,38,41,43,45],stringproperti:[11,32],strongli:4,structur:[3,6,12,23,29],stub:43,stuff:3,style:[4,12,14,41],sub:[4,10,24,33,41],subclass:[2,14,21,24,33,36,41],subj:8,subload:12,submit:8,submodul:[17,18],subpackag:[17,18],subqueri:[23,41],subsequ:3,subset:8,succeed:29,success:[3,6],successfulli:[3,36],such:[2,3,4,10,22,23,26,27,29],suffici:3,suffix:7,sugar:4,suit:[23,41],summari:4,support:[2,3,5,8,11,12,17,20,21,23,26,29,33,37,41,43,44],support_prepar:[26,28],support_return:[26,28],supports_native_decim:[26,27],suppos:[2,7,24,33,36],sure:[3,4,8,12,29,38],surpris:3,svx:8,swagger:44,sweet:1,symbol:21,sync:[3,10],sync_engin:3,sync_safe_connect:3,synchron:21,syntax:8,sys:31,system:[2,10,12,44],t1:3,tabl:[5,6,7,12,14,15,21,23,24,26,27,34,41,44],table_nam:[24,26,27],tableclaus:14,take:[2,4,7,10,13,14,23,29,33],taken:[2,29,38],talk:4,target_metadata:[6,10,44],task:[2,4,17,29,39],tast:14,team:[23,41],team_id:23,technic:23,telegram:43,tell:[2,3],temporarili:3,ten:4,termin:8,test:[3,8,41,44],test_aiohttp:8,test_crud:44,test_gino:8,test_us:44,testclient:44,text:[3,8,10,11,12,14,21,33,41],textual:[2,12],than:[2,3,4,7,10,12,13,15,23,24,29,43],thank:[2,41],that:[1,2,3,4,6,8,10,12,14,15,21,22,23,24,26,27,29,31,33,38,39,41],the:[0,1,2,4,5,6,8,11,12,13,14,15,19,21,22,23,24,26,27,29,31,33,36,38,39,41,43,44,45],thei:[2,3,4,7,8,10,12,14,15,23,24,29,41,45],their:[2,3,4,10,12,13,14,21,23,24],them:[3,4,6,7,10,11,12,23,41],themselv:7,then:[2,3,4,7,8,10,12,14,23,26,27,29,33,39,41],theoret:[4,41],there:[1,2,3,4,5,7,12,13,14,15,22,23,29,36,41],therefor:[2,4,7,10,14,21,23,29,38],these:[2,4,6,7,8,21,23,24,26,29,41],thi:[2,3,4,6,7,8,10,11,12,14,15,20,21,22,23,24,26,27,29,31,33,35,36,38,39,40,41,45],thing:[2,3,4,6,7,14,24,41],think:[3,4,10],third:[12,14],those:[4,29],though:[3,4,14,23,38],thought:3,thousand:4,thread:[1,2,3,4],three:2,through:[2,5,8,12,15,21,31,41],throughput:4,thu:[2,4,14,15,29,41],tiago:41,tiangolo:43,tiger:3,tim:43,time:[1,2,3,4,6,10,11,12,15,19,21,23,26,29,38],timeout:[7,21,23,26,27,28,29,41],timeouterror:29,timestamp:11,timezon:24,tini:4,tip:5,titl:[43,44],to:[0,1,2,3,5,6,8,11,13,14,15,17,19,20,21,22,23,24,26,27,29,31,33,36,38,39,43,44,45],to_dict:[23,41,44],togeth:[2,3,29,36],token:4,toml:44,toni:[41,43],too:[2,3,4,10,14,15,29],took:3,tool:[4,6,43,44],toolkit:6,top:[2,3,4,10,14,29],tornado:[17,37,41,43],tortois:43,total:4,touch:[15,36],touchabl:2,tox:8,trackedmixin:24,tradit:[10,14],transact:[2,3,4,5,10,12,17,18,19,21,26,27,28,29,38,43,44],transaction_isol:3,transform:12,transit:3,translat:[2,11,12],trap:[15,36],travers:2,traverse_singl:34,treat:[2,3,7,12,24,29,35],tree:12,tri:[4,29,41],trigger:[4,24],trio:10,ts:33,tupl:[2,10,12,23,29,33],tupleload:[10,12,33,41],turn:[1,2,3,12,29,38,41],tutori:[14,43,44],twice:[2,5,26,27],twist:[4,43],two:[2,4,7,10,12,14,15,23,29,36],tx1:[15,36],tx2:[15,36],tx3:36,tx:[15,27,29,36,41],txt:8,type:[2,5,10,11,12,14,23,24,26,29,33,36,41],type_nam:27,typic:[4,10,26],ua1:12,ua2:12,ubuntu:43,uc:43,ui:44,uid:[7,12,44],ultim:4,ultra:43,um:1,unbind:21,under:[2,3,10,12,14,21,23,24,33,41,44],underli:[2,3,14,15,29,36,41],unfortun:[3,4],unicod:[6,10,12,14,24,26,27,38,44,45],unifi:[2,41],uninitializederror:[10,30,41],uniqu:[2,12,45],unique_constraint:24,unique_id:24,uniqueconstraint:24,unix:43,unix_socket:26,unknown:[12,41],unknownjsonpropertyerror:30,unless:[2,4,23],unnam:[41,44],unnecessarili:38,unpin:41,unpredict:[4,38],unrecogn:39,unreli:4,unreus:2,unset:2,unspecifi:23,untest:2,until:[10,11,29],unus:[2,4],unwant:14,unwrap:[26,27],up:[1,2,5,8,10,12,21,26,27,29,36,39],updat:[2,3,5,8,11,23,24,29,33,36,41,43,44,45],update_execution_opt:[29,41],updaterequest:[23,45],upgrad:[2,3,6,10,41,44],upon:[3,15,26,27,36],upstream:[3,10],urh:1,url:[2,6,21,26,27,28,35,39,41,44,45],us:[3,4],usabl:[2,29,41],usag:[2,5,6,23,29,39,41],use:[2,3,4,5,6,11,12,13,14,15,21,22,23,26,27,29,31,33,36,38,39,41,43,44],use_connection_for_request:[39,44],use_unicod:26,used:[2,3,7,10,12,13,14,21,22,23,24,26,27,29,33,41],useful:[2,4,7,14,23,29,33,36],user1:23,user2:23,user:[2,3,4,5,6,7,8,11,12,13,14,21,23,24,26,27,29,33,36,38,39,43,44,45],user_gett:7,user_id:[10,12,14,23,38],user_queri:7,user_t:7,usermodel:44,usernam:[6,44],users_t:2,uses:[3,10,12,23,35],using:[2,3,4,7,8,10,11,12,14,15,23,24,26,27,29,33,38,41],usual:[2,3,4,7,10,11,12,14,21,23,24,29],utc:12,util:[3,24,31],uuid4:44,uuid:44,uvicorn:[43,44],uvicornwork:44,uvloop:[1,43],v7oze:29,val:[10,11,32],valid:[3,14],valu:[2,3,7,10,12,14,15,21,23,24,26,27,29,32,33,38,41,45],valueload:33,van:41,vanilla:[2,14],vargovcik:41,variabl:[6,43],variant:2,ve:[1,3,4,12],venv:44,veri:[1,2,3,4,11,12,14,23,26,27,29,36,38],verifi:3,version:[3,6,8,10,19,20,39,41,44],view:[26,44],virtual:[3,8],virtualenv:44,virtualenvwrapp:8,visit:[10,12,21],visit_foreign_key_constraint:34,visit_index:34,visit_metadata:34,visit_sequ:34,visit_t:34,visual:43,vladimir:41,volkova:41,volunt:8,wai:[2,3,4,8,10,11,12,14,15,21,29],wait:[3,4,11,15,23,29,44],wang:[41,43],wanna:[1,4],want:[2,3,4,5,6,8,14,21,23,29,38],ware:10,warehous:22,warn:[41,44],was:[2,3,4,12,15,26,29,36],wast:[2,4],watch:[1,43],we:[2,3,4,6,7,10,11,12,14,15,21,38,41],weakref:41,web:[7,10,41,43],websit:8,weird:3,welcom:[8,14,41,43],well:[1,2,3,4,24],were:4,what:[1,2,3,4,5,14,15,21],whatev:[2,4,12,23,29],wheel:4,when:[0,1,2,3,6,7,8,10,12,14,15,19,20,21,22,23,24,26,29,33,36,38,41],where:[2,3,4,7,10,11,12,14,21,23,29,33,36,45],whether:[29,36,41],which:[2,3,4,7,10,12,14,21,22,23,24,26,27,29,36,38,41],whichev:41,whole:41,whose:[3,14,36],why:[4,12],wide:[3,26,27],wiki:[1,43],wikipedia:[1,43],will:[2,3,4,5,6,7,8,11,12,15,21,22,23,24,26,27,29,31,33,35,36,39,41,44,45],wip:[38,40],wire:3,wise:[3,4],wish:[2,8,12,29],with_bind:[2,7,10,12,14,21,22,41],with_tabl:[7,24,41],within:[3,4,15,26,27,29,38,41],without:[2,3,4,7,11,14,15,21,29,41],won:[1,2,3,4,7,10,14,15,22,36,41],wonder:1,word:3,work:[2,3,4,5,6,8,12,14,17,23,24,26,29,37,41],workaround:[3,10],workdir:44,worker:44,world:[3,14,38],worri:[2,4,10,15,41],worth:[2,14],would:[3,4,10,12,21,23,33],wrap:[3,10,24],wrapper:[2,3,4,10,24,29,43],write:[2,4,10,12],written:[14,43],wrong:[4,41],wrote:4,ww4ronfhiqi:43,www:43,wysiwyg:3,x509:8,xss:44,xxx:[23,43],xxxx:5,yai:[1,10],yeah:3,year:3,yes:[2,3],yet:[1,10,11,29],yield:[4,14,23,24,29,33,44],yml:44,you:[1,2,3,4,6,7,8,10,11,12,14,15,21,22,23,24,29,36,38,39,41],your:[1,2,3,4,6,8,10,12,14,15,21,29,41,44],your_name_her:8,yourdbnam:44,youtub:43,yurii:41,za:41,zen:43,zenof:43,zero:[23,29],zh:43,zone:[11,12]},titles:["\u539f\u7406\u8bf4\u660e","\u5f02\u6b65\u7f16\u7a0b\u57fa\u7840","\u5f15\u64ce\u4e0e\u8fde\u63a5","SQLAlchemy 2.0","\u4e3a\u4ec0\u4e48\u8981\u7528\u5f02\u6b65 ORM\uff1f","\u8fdb\u9636\u7528\u6cd5","\u4f7f\u7528 Alembic","\u9884\u5236\u67e5\u8be2","\u8d21\u732e","\u589e\u5220\u6539\u67e5","\u5e38\u89c1\u95ee\u9898","JSON \u6269\u5c55\u5c5e\u6027","\u52a0\u8f7d\u5668\u4e0e\u5173\u7cfb","\u8fde\u63a5\u6c60","\u8868\u7ed3\u6784\u5b9a\u4e49","\u6570\u636e\u5e93\u4e8b\u52a1","\u6b22\u8fce\u6765\u5230 GINO \u7684\u6587\u6863\uff01","\u53c2\u8003\u624b\u518c","API \u53c2\u8003","gino package","gino.aiocontextvars module","gino.api module","gino.bakery module","gino.crud module","gino.declarative module","gino.dialects package","gino.dialects.aiomysql module","gino.dialects.asyncpg module","gino.dialects.base module","gino.engine module","gino.exceptions module","gino.ext package","gino.json_support module","gino.loader module","gino.schema module","gino.strategies module","gino.transaction module","\u6269\u5c55","Sanic Support","Starlette Support","Tornado Support","\u7248\u672c\u5386\u53f2","\u4e0a\u624b\u6559\u7a0b","\u5b98\u5ba3\uff1aPython \u5f02\u6b65\u7f16\u7a0b\u518d\u6dfb\u4e00\u5229\u5668","\u642d\u5efa\u4e00\u4e2a FastAPI \u670d\u52a1\u5668","GINO \u57fa\u7840\u6559\u7a0b"],titleterms:{"02":41,"03":41,"04":41,"05":41,"06":41,"07":41,"08":41,"09":41,"10":41,"11":41,"12":41,"14":41,"15":41,"16":41,"18":41,"19":41,"20":41,"2017":41,"2018":41,"2019":41,"2020":41,"21":41,"23":41,"24":41,"25":41,"26":41,"28":41,"do":10,"for":[3,10],"in":10,"with":[7,10,38,39],access:4,admin:10,advanc:12,aiocontextvar:[10,20],aiomysql:26,alemb:[6,10,44],and:10,api:[3,7,18,21,41,44],appli:6,are:7,async:3,asynchron:4,asyncio:[4,10],asyncpg:27,auto:3,autocommit:3,avail:7,bakedqueri:7,bakeri:[7,22],bare:7,base:28,basic:15,batch:10,be:[4,10],bulk:10,can:10,column:10,commit:3,complex:10,complic:3,configur:39,connect:[2,10,39],content:[19,25,31],contextvar:41,contribut:8,control:15,core:14,creat:[2,6,11],crud:23,current_connect:2,custom:7,databas:[4,10],db:[3,6],declar:24,defin:10,develop:41,dialect:[25,26,27,28],differ:10,django:10,doe:10,don:[4,7],done:4,earli:41,engin:[2,7,10,14,29,41],except:30,execut:[2,10],exist:10,explicit:4,express:12,ext:31,fastapi:44,featur:10,first:6,fly:10,get:8,gino:[7,10,14,16,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,41,43,44,45],ginoconnect:41,guidelin:8,help:4,hook:11,how:[4,7,10],implicit:2,index:[10,11],initi:10,insert:10,integr:7,interfac:10,is:10,isol:3,it:10,join:10,json:11,json_support:32,lazi:[2,39],level:3,load:10,loader:[7,12,33],local:41,manag:2,mani:12,manual:15,migrat:[6,41],model:[12,44],modul:[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],multipl:10,nest:15,none_as_non:41,not:10,of:[8,10,12],on:[6,7,10,11],one:12,or:10,orm:[4,10,14],other:12,packag:[19,25,31],paramet:10,pend:41,prepar:7,print:10,product:4,properti:11,pull:8,python:43,queri:[2,10,41],quick:11,raw:10,referenc:12,relationship:[10,12],releas:41,request:8,result:10,reus:2,reusabl:2,revis:6,right:4,run:10,same:10,sanic:38,schema:34,self:12,set:6,solut:3,sql:10,sqlalchemi:[3,10],ssl:10,starlett:39,start:[8,11],starv:4,statement:7,strategi:35,submodul:[19,25],subpackag:19,support:[10,38,39,40],tabl:10,task:41,the:[3,7,10],there:10,through:10,tip:8,to:[4,7,10,12,41],tornado:40,transact:[15,36,41],twice:10,type:8,up:6,updat:10,usag:[12,15],use:[7,10],user:10,want:7,what:[7,10],when:4,will:10,work:[10,38,39],xxxx:10}}) \ No newline at end of file diff --git a/docs/zh/master/tutorials.html b/docs/zh/master/tutorials.html new file mode 100644 index 0000000..ff2bdf2 --- /dev/null +++ b/docs/zh/master/tutorials.html @@ -0,0 +1,241 @@ + + + + + + + + 上手教程 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/tutorials/announcement.html b/docs/zh/master/tutorials/announcement.html new file mode 100644 index 0000000..fd5735b --- /dev/null +++ b/docs/zh/master/tutorials/announcement.html @@ -0,0 +1,497 @@ + + + + + + + + 官宣:Python 异步编程再添一利器 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    官宣:Python 异步编程再添一利器

    +
    +

    GINO 填补了国内外 asyncio ORM 领域的空白

    +
    +

    随着 Tornado1 和 asyncio2 等框架的陆续涌现,Python 异步编程这个话题也在逐渐升温。在这个烧脑的异步世界里,有没有办法可以既方便快捷、又简单明了地访问数据库呢?GitHub 千星项目 +GINO 了解一下!

    +../_images/python-gino.webp +
    +

    GINO 是谁

    +

    GINO 是一个“轻量级”异步 ORM 框架,它的全称是 GINO Is Not ORM,借鉴了 GNU is Not Unix +的递归定义3手法。所以,GINO 一定要全!部!大!写!如果像这样“Gino”就变成了人名,你肯定要问一句“这是谁”。

    +

    ORM,即关系对象映射(Object-Relational Mapping4),是一类开发人员喜闻乐见的效率工具,它们“极大地”提升了写代码的幸福指数。GINO 是用来访问数据库的,也提供了对象映射的工具,那为什么非说 +GINO 不是 ORM 呢?

    +

    因为物极必反,ORM 在带来生活便利的同时,也是 bug 生长的温床——传统 ORM 往往会选择牺牲明确性(explicitness)来换取便捷性(convenience),再加上 Python 得天独厚的灵活性(flexibility),创造出了一种爆炸式的化学反应。一旦代码初具规模,项目或多或少都会遇到 ORM 反噬的情景:性能莫名其妙的差、出问题找不到原因、为了鸡毛蒜皮的小事大动干戈。随便一句 current_user.name +都有可能触发一大堆意想不到的数据库调用,这代码你让我怎么调试?

    +

    传统 ORM 的学习曲线前平后陡,能在快速原型开发中大展身手,但应用到大型项目中却十分考验开发人员的平均水平。

    +

    所有这些问题如果再放进异步编程的环境里,那就是 O(n2) 的复杂度了——哦不,是 +O(2n)。这对于一款优秀的异步 ORM 框架来说是不可接受的,所以 GINO 是 ORM 但不是一个传统的 +ORM,正犹如 GNU 不是一个传统的 Unix 一样,形似而神不似。

    +

    所以在 2017 年创作之初,我就给 GINO 定下了两个业绩目标:1) 方便快捷,2) 简单明了。三年后的今天,我索性在 1.0 稳定版发布的前夕做个年终总结。

    +
    +
    +

    先说“方便快捷”

    +
    +

    “方便快捷”主要说的是开发效率。

    +
    +

    重视开发效率的概念对于写 Python 的同学来说可能并不陌生,某些场景下,开发人员的时间确实比机器的时间值钱。所以,传统 ORM 里的对象映射不能丢。

    +
    from gino import Gino
    +db = Gino()
    +class User(db.Model):
    +    __tablename__ = "users"
    +    id = db.Column(db.Integer, primary_key=True)
    +    name = db.Column(db.String)
    +
    +
    +

    这么定义表结构甚至让人有点小兴奋。咦,为什么这么眼熟?

    +

    没有错,这就是 SQLAlchemy ORM5 的定义风格。GINO 并不是从头造轮子,而是在 SQLAlchemy +core6(SQLAlchemy 中负责构建 SQL 的底层核心)的基础上开发的。这么做除了能保持熟悉的味道(以节省学习和迁移成本),更重要的是带来了整个 SQLAlchemy 的生态环境:开箱即用的数据库变更管理工具 +Alembic7、各种 SQLAlchemy 的增强插件8、专业领域的 PostGIS/geoalchemy9等,GINO 全都兼容。

    +

    是不是十分方便、十分快捷?不止这样。

    +

    GINO 一站式地解决了常用 CRUD 快捷方式10、上下文管理(aiocontextvars1112)、 +数据库事务封装和嵌套13、连接池管理和懒加载14等多项便捷功能,无额外依赖关系,即装即用。

    +
    daisy = await User.create(name="daisy")
    +await daisy.update(name="Daisy").apply()
    +
    +
    +

    GINO 还提供了15各大流行异步 Web 框架的定制版插件,能叫上名字的像 Tornado1、aiohttp16、Sanic17、FastAPI18/Starlette19、Quart20什么的都有,从简单示范到生产环境的各种例子品种齐全,妈妈再也不用担心我不会集成 Web 框架了。

    +

    为了让不同应用场景下的用户体验到最大的善意,GINO 目前支持三种不同程度的用法,成功实现了对同期竞品 +asyncpgsa21 的降维打击:

    +
      +
    1. 最少侵入型22:SQLAlchemy core 原教旨主义者,只有异步执行时才用到 GINO。

    2. +
    3. 终身不婚型23:天生厌恶“对象”,只愿定义“表”,空手接 SQL。

    4. +
    5. 火力全开型24:最大程度的便利,非典型异步 ORM。

    6. +
    +

    最后,虽然是 Python(绝不是黑哈),但 GINO 在执行效率上也没落下。基于 MagicStack 出品必属精品的、一秒可读百万行的 asyncpg25,以及 uvloop26(可选)的强力加持,GINO +跑起来也是可以飞快的,被广泛应用于诸如实时汇率、聊天机器人、在线游戏等高并发领域,深受俄罗斯和乌克兰人民的爱戴。

    +
    +
    +

    再说“简单明了”

    +
    +

    Explicit is better than implicit.

    +

    Simple is better than complex.

    +

    –The Zen of Python, PEP 2027

    +
    +

    Python 之禅完美表达了 GINO 的立场——明确性(explicitness)对于上了规模的异步工程项目来说尤为重要,因此 GINO 的很多设计都受到了明确性的影响。

    +

    比如说,GINO 的 Model 是完全无状态的普通 Python 对象(POPO28—— 例如前面的 User +类,它的实例 daisy 就是内存里面的常规对象,你可以用 daisy.name 访问属性,也可以用 +daisy.name = "DAISY" 来修改属性,或者用 u = User() 来创建新的实例,这些操作都不会访问数据库,绝对绿色环保无毒副作用。

    +

    等到需要操作数据库的时候,你一定会有感知的。比如执行 INSERT 要用 +u = await User.create(),而 UPDATE 则是 +await u.update(name="Daisy").apply()

    +
    +

    提示

    +

    其中,u.update(name="Daisy") 与 u.name = "Daisy" 类似,都是只在内存里修改对象的属性,不同的是 u.update() 还会返回一个包含本次变更的中间结果,对其执行 await xxx.apply() 则会将这些变更应用到数据库里。

    +
    +

    这里的 await 就是明确性的关键,意味着我们要跳出去执行数据库操作了。换句话说,没有 await +就没有数据库操作。

    +

    另一方面,对于如何将数据库查询结果组装成内存对象及其属性,GINO +也有一套精妙的显式机制——可定制化的加载器 loaders29。对于简单直观的一对一加载,GINO +自然是伺候到家的,比如用 u = await User.get(1) 可以直接获取到 ID 为 1 的用户对象。但是对于更复杂的查询,GINO 不会去无端猜测主人的意图,而全权交给用户来明确地定义。加载器的用法也是很简单的,比如一个用户可能写了很多本书:

    +
    class Book(db.Model):
    +    __tablename__ = "books"
    +    id = db.Column(db.Integer, primary_key=True)
    +    title = db.Column(db.String)
    +    author_id = db.ForeignKey("users.id")
    +
    +
    +

    然后这样来加载这种多对一关系,以同时获取所有的书和他们的作者:

    +
    query = Book.outerjoin(User).select()
    +loader = Book.load(author=User)
    +async for book in query.gino.load(loader).iterate():
    +    print(book.title, "written by", book.author.name)
    +
    +
    +

    很简单的一个外连接查询 Book.outerjoin(User),配合一个直观的加载器 +Book.load(author=User),就实现了:

    +
      +
    1. 执行 SELECT * FROM books LEFT JOIN users ON ...

    2. +
    3. 将数据库返回结果的每一行中,属于 books 的字段加载成一个 Book 实例;

    4. +
    5. 然后将该行中剩下的属于 users 的字段加载成一个 User 实例;

    6. +
    7. 最后将 User 实例设置到 Book 实例的 author 属性上。

    8. +
    +

    既简单又明了有没有!你甚至可以手写任何 SQL,然后定制加载器自动加载成期望的对象关系,精准控制加载行为,指哪儿打哪儿。GINO 还有很多类似的特性,在这里就不一一列举了。

    +
    +
    +

    优势与不足

    +

    随着这几年 GINO 不断演进成熟,Python 开源社区里也相继出现了像 Tortoise +ORM30、ORM31(是的,这个项目就叫 ORM……我真 ORZ。出品方是 Encode,Starlette +就是他们的作品)等优秀的异步 ORM 框架。它们关注的重点与 GINO 稍有不同,但都是同行就不多评价了。

    +

    GINO 的最大优势还是在于充分平衡了开发效率和明确性之间的辩证矛盾关系,用 GINO 开发应用程序的时候不用担心会被意料之外的行为所惊吓到,同时也不需要为这种明确性付出过大的工程代价,上手后依然可以快速、快乐地编程。同时,大量的成功案例也证明了 GINO 已经初步具备发布 1.0 稳定版的各种条件,可以谨慎地用于生产环境了。

    +

    以下是近来统计到的关于 GINO 的应用案例:

    + +

    另外,GINO 还贴心地提供了中文文档32,从上手教程到原理说明应有尽有(虽然文档还在努力编写中!):

    +../_images/docs.webp +

    GINO 目前的不足之处还有一些,比如没有照顾到 Python 3 的类型提示,因此还不能完全发挥 IDE 的潜能(上面那个 gino-stubs 就是有人受不了了自己写了一个类型注解)。MySQL 目前也是不支持的,但 GINO +从比较早就解耦了不同 SQL 方言和驱动的集成,所以这些功能会陆续在 1.1 和 1.2 版本中跟上。

    +
    +
    +

    建设社会主义

    +

    GINO 是一个开源项目,所以欢迎大家一起来建设!长期活跃的贡献者还能获赠价值 4888 元的 PyCharm +专业版全家桶 License 一枚33(没有发票)。

    +

    目前急需帮助的有:

    +
      +
    1. 各个 Web 框架插件的维护工作需要多人认领;

    2. +
    3. 更多的例子和文档,以及中文、俄文的翻译;

    4. +
    5. MySQL 的支持。

    6. +
    +

    以及下面这些一直需要的帮助:

    +
      +
    1. 用 GINO,找 bug,提建议;

    2. +
    3. 修 bug,做功能,提 PR;

    4. +
    5. 维护社区,回答问题,参与讨论;

    6. +
    7. 最后也是最重要的:去 GitHub 上给 GINO 加一颗星星!

    8. +
    +../_images/happy-hacking.png +
    +
    +

    关于作者

    +

    87 年生人,6 岁开始接触编程,二十五六年的编程史和十三四年的工作经验教会了我许多软件开发的奥义。小时候写 GBasic、QBasic 和 Visual Basic;大学里开始写 Java 并接触到了 FreeBSD 和 Ubuntu +等开源项目且一发不可收拾;工作头五年转向了 Python,通过 Twisted 和 Eventlet 等项目了解了异步编程,期间贡献了 Gevent 的 Python 3 迁移;也曾在创业的潮流中留下身影,亲身经历并见证了软件技术随着手游、新媒体、矿圈、互金、电商、社交、文娱、汽车等行业的起起伏伏;目前在芝大继续做生物大数据相关的开源项目。业余时间除了开发维护 GINO 项目外,还会偶尔修一修 uvloop、asyncpg 和 CPython 的 bug :P

    +

    特别感谢 Tony Wang 对 GINO 项目的贡献,以及所有人的贡献。

    +
    +
    +

    参考文献

    +
    +
    1(1,2)
    +

    Facebook. Tornado. https://zh.wikipedia.org/wiki/Tornado.

    +
    +
    2
    +

    Python Software Foundation. asyncio — 异步 I/O. +https://docs.python.org/zh-cn/3/library/asyncio.html.

    +
    +
    3
    +

    维基百科. GNU. https://zh.wikipedia.org/wiki/GNU.

    +
    +
    4
    +

    维基百科. 对象关系映射. +https://zh.wikipedia.org/wiki/对象关系映射.

    +
    +
    5
    +

    维基百科. SQLAlchemy. https://zh.wikipedia.org/wiki/SQLAlchemy.

    +
    +
    6
    +

    Michael Bayer. SQLAlchemy Core - SQLAlchemy. Documentation. +https://docs.sqlalchemy.org/en/13/core/index.html.

    +
    +
    7
    +

    Michael Bayer. Welcome to Alembic’s documentation!. +https://alembic.sqlalchemy.org/en/latest/.

    +
    +
    8
    +

    Hong Minhee. A curatedlist of awesome tools for SQLAlchemy. +https://github.com/dahlia/awesome-sqlalchemy.

    +
    +
    9
    +

    Ricardo GarciaSilva. Does it have postgis support?. +https://github.com/python-gino/gino/issues/627.

    +
    +
    10
    +

    Fantix King. GINO 基础教程 - 增删改查. +https://python-gino.org/docs/zh/master/tutorials/tutorial.html#crud-operations.

    +
    +
    11
    +

    Python Software Foundation. contextvars —Context Variables. +https://docs.python.org/3/library/contextvars.html.

    +
    +
    12
    +

    Fantix King. gino/aiocontextvars.py. +https://github.com/python-gino/gino/blob/f2c273a43a3ed893a767d4239046f2befabf510d/src/gino/aiocontextvars.py.

    +
    +
    13
    +

    Fantix King. 数据库事务. +https://python-gino.org/docs/zh/master/how-to/transaction.html.

    +
    +
    14
    +

    Fantix King. 引擎与连接. +https://python-gino.org/docs/zh/master/explanation/engine.html.

    +
    +
    15
    +

    GINO Community. GINO Community. https://github.com/python-gino/.

    +
    +
    16
    +

    aiohttp maintainers. Welcome to AIOHTTP. https://docs.aiohttp.org/en/stable/.

    +
    +
    17
    +

    Sanic Community. SanicFramework. https://sanicframework.org/.

    +
    +
    18
    +

    Sebastián Ramírez. FastAPI. https://fastapi.tiangolo.com/.

    +
    +
    19
    +

    Encode OSS. Starlette. https://www.starlette.io/.

    +
    +
    20
    +

    Philip Jones. Quart Documentation. https://pgjones.gitlab.io/quart/.

    +
    +
    21
    +

    Canopy. asyncpgsa - A wrapper around asyncpg for use with sqlalchemy. +https://github.com/CanopyTax/asyncpgsa.

    +
    +
    22
    +

    Fantix King. 表结构定义 -GINO 引擎. +https://pythongino.org/docs/zh/master/how-to/schema.html#gino-engine.

    +
    +
    23
    +

    Fantix King. 表结构定义 - GINO core. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-core.

    +
    +
    24
    +

    Fantix King. 表结构定义 - GINO ORM. +https://python-gino.org/docs/zh/master/how-to/schema.html#gino-orm.

    +
    +
    25
    +

    MagicStack Inc. +asyncpg - A fast PostgreSQL Database Client Library for Python/asyncio. +https://github.com/MagicStack/asyncpg.

    +
    +
    26
    +

    MagicStack Inc. uvloop -Ultra fast asyncio event loop. +https://github.com/MagicStack/uvloop.

    +
    +
    27
    +

    Tim Peters. PEP ‒ The Zenof Python. https://www.python.org/dev/peps/pep-0020/.

    +
    +
    28
    +

    Wikipedia. Plain old Java object. +https://en.wikipedia.org/wiki/Plain_old_Java_object.

    +
    +
    29
    +

    Fantix King. 加载器与关系. +https://python-gino.org/docs/zh/master/how-to/loaders.html.

    +
    +
    30
    +

    Andrey Bondar. Tortoise ORM. https://tortoise.github.io/.

    +
    +
    31
    +

    Encode OSS. ORM - An async ORM. https://github.com/encode/orm.

    +
    +
    32
    +

    Fantix King. 欢迎来到 GINO 的文档!. +https://python-gino.org/docs/zh/master/index.html.

    +
    +
    33
    +

    JetBrains s.r.o. Open Source Licenses. +https://www.jetbrains.com/community/opensource/#support.

    +
    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/tutorials/fastapi.html b/docs/zh/master/tutorials/fastapi.html new file mode 100644 index 0000000..fe6e707 --- /dev/null +++ b/docs/zh/master/tutorials/fastapi.html @@ -0,0 +1,674 @@ + + + + + + + + 搭建一个 FastAPI 服务器 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    搭建一个 FastAPI 服务器

    +

    在这篇教程里,我们会一起搭建一个用于生产环境的 FastAPI 服务器。完整的示例代码在这里

    +

    写好之后,整个应用技术栈会是这样的:

    +../_images/gino-fastapi.svg
    +

    创建一个新项目

    +

    这里我们尝试用亮瞎眼的 Poetry 来管理我们的项目,而不是传统的 pip。请跟随链接安装 Poetry,并且在一个空文件夹中创建我们的新项目:

    +
    $ mkdir gino-fastapi-demo
    +$ cd gino-fastapi-demo
    +$ git init
    +$ poetry init
    +
    +
    +

    然后跟着 Poetry 的向导完成初始化——关于交互式创建依赖的两个问题,您可以回答“no”,因为我们会在下面手动创建。其他问题都可以用默认值,只是一定保证包的名字是 gino-fastapi-demo

    +
    +
    +

    添加依赖关系

    +

    FastAPI 底层用的是 Starlette 框架,所以我们就可以直接使用 GINO 的 Starlette 扩展。执行以下命令即可:

    +
    $ poetry add gino[pg,starlette]
    +
    +
    +

    接着我们添加 FastAPI,以及快成一道闪电的 ASGI 服务器 Uvicorn,还有用作生产环境的应用服务器 Gunicorn

    +
    $ poetry add fastapi uvicorn gunicorn
    +
    +
    +

    我们将用 Alembic 来管理数据库表结构变更。因为 Alembic 只兼容传统的 DB-API 驱动,所以我们还得加上 psycopg

    +
    $ poetry add alembic psycopg2
    +
    +
    +

    最后,测试框架选用 pytest,我们将其添加到开发环境的依赖关系中。同时也加上 requests 库,因为 StarletteTestClient 要用到它:

    +
    $ poetry add -D pytest requests
    +
    +
    +
    +

    提示

    +

    经过了上面的步骤,Poetry 会悄没声地帮我们自动创建一个 virtualenv,并且把所有的依赖关系装到这个虚拟环境里。在本教程后面的步骤里,我们会假定继续使用这个环境。但是,您也可以创建自己的 virtualenv,只要激活了 Poetry 就会用它。

    +
    +

    以上。下面是 Poetry 给我创建出来的 pyproject.toml 文件内容,您的应该也长得差不多:

    +
    [tool.poetry]
    +name = "gino-fastapi-demo"
    +version = "0.1.0"
    +description = ""
    +authors = ["Fantix King <fantix.king@gmail.com>"]
    +
    +[tool.poetry.dependencies]
    +python = "^3.8"
    +gino = {version = "^1.0", extras = ["pg", "starlette"]}
    +fastapi = "^0.54.1"
    +uvicorn = "^0.11.3"
    +gunicorn = "^20.0.4"
    +alembic = "^1.4.2"
    +psycopg2 = "^2.8.5"
    +
    +[tool.poetry.dev-dependencies]
    +pytest = "^5.4.1"
    +requests = "^2.23.0"
    +
    +[build-system]
    +requires = ["poetry>=0.12"]
    +build-backend = "poetry.masonry.api"
    +
    +
    +../_images/gino-fastapi-poetry.svg

    同时自动生成的还有一个叫 poetry.lock 的文件,内容是当前完整依赖关系树的精确版本号,当前的目录结构如右图所示。现在让我们把这两个文件加到 Git 仓库中(以后的步骤就不再演示 Git 的操作了):

    +
    $ git add pyproject.toml poetry.lock
    +$ git commit -m 'add project dependencies'
    +
    +
    +
    +
    +

    编写一个简单的服务器

    +

    现在让我们写一点 Python 的代码吧。

    +

    我们要创建一个 src 文件夹,用来装所有的 Python 文件,如下图所示。这种目录结构叫做“src 布局”,能让项目结构更清晰。

    +../_images/gino-fastapi-src.svg

    我们项目的顶层 Python 包叫做 gino_fastapi_demo,我们在里面创建两个 Python 模块:

    +
      +
    • asgi 作为 ASGI 的入口,将被 ASGI 服务器直接使用

    • +
    • main 用来初始化我们自己的服务器

    • +
    +

    下面是 main.py 的内容:

    +
    from fastapi import FastAPI
    +
    +def get_app():
    +    app = FastAPI(title="GINO FastAPI Demo")
    +    return app
    +
    +
    +

    asgi.py 里,我们只需要实例化我们的应用即可:

    +
    from .main import get_app
    +
    +app = get_app()
    +
    +
    +

    然后执行 poetry install 来把我们的 Python 包以开发模式链接到 PYTHONPATH 中,接下来就可以启动 Uvicorn 的开发服务器了:

    +
    $ poetry install
    +Installing dependencies from lock file
    +
    +No dependencies to install or update
    +
    +  - Installing gino-fastapi-demo (0.1.0)
    +
    +$ poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    +INFO:     Started reloader process [53010]
    +INFO:     Started server process [53015]
    +INFO:     Waiting for application startup.
    +INFO:     Application startup complete.
    +
    +
    +

    这里的 --reload 选项会启用 Uvicorn 的自动加载功能,当我们的 Python 代码发生变动的时候,Uvicorn 会自动加载使用新代码。现在可以访问 http://127.0.0.1:8000/docs 了,试一下我们新 FastAPI 服务器的 Swagger UI 接口文档。

    +
    +

    提示

    +

    正如之前提到的,如果您使用自己的虚拟环境,那么此处的 poetry run uvicorn 就可以简化为 uvicorn

    +

    poetry run 是一个快捷命令,用于在 Poetry 管理的虚拟环境中执行后续的命令。

    +
    +
    +
    +

    添加 GINO 扩展

    +../_images/gino-fastapi-config.svg

    现在让我们把 GINO 添加到服务器里。

    +

    首先,我们需要有办法来配置数据库。在本教程中,我们选用 Starlette配置系统。创建文件 src/gino_fastapi_demo/config.py,内容为:

    +
    from sqlalchemy.engine.url import URL, make_url
    +from starlette.config import Config
    +from starlette.datastructures import Secret
    +
    +config = Config(".env")
    +
    +DB_DRIVER = config("DB_DRIVER", default="postgresql")
    +DB_HOST = config("DB_HOST", default=None)
    +DB_PORT = config("DB_PORT", cast=int, default=None)
    +DB_USER = config("DB_USER", default=None)
    +DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    +DB_DATABASE = config("DB_DATABASE", default=None)
    +DB_DSN = config(
    +    "DB_DSN",
    +    cast=make_url,
    +    default=URL(
    +        drivername=DB_DRIVER,
    +        username=DB_USER,
    +        password=DB_PASSWORD,
    +        host=DB_HOST,
    +        port=DB_PORT,
    +        database=DB_DATABASE,
    +    ),
    +)
    +DB_POOL_MIN_SIZE = config("DB_POOL_MIN_SIZE", cast=int, default=1)
    +DB_POOL_MAX_SIZE = config("DB_POOL_MAX_SIZE", cast=int, default=16)
    +DB_ECHO = config("DB_ECHO", cast=bool, default=False)
    +DB_SSL = config("DB_SSL", default=None)
    +DB_USE_CONNECTION_FOR_REQUEST = config(
    +    "DB_USE_CONNECTION_FOR_REQUEST", cast=bool, default=True
    +)
    +DB_RETRY_LIMIT = config("DB_RETRY_LIMIT", cast=int, default=1)
    +DB_RETRY_INTERVAL = config("DB_RETRY_INTERVAL", cast=int, default=1)
    +
    +
    +

    这个配置文件会首先从环境变量中加载配置参数,如果没找到,则会从当前路径(通常是项目顶层目录)下一个叫 .env 的文件中加载,最后不行才会使用上面定义的默认值。比如,您即可以在命令行中设置:

    +
    $ DB_HOST=localhost DB_USER=postgres poetry run uvicorn gino_fastapi_demo.asgi:app --reload
    +
    +
    +../_images/gino-fastapi-env.svg

    也可以在 .env 文件中设置(一定不要将该文件提交到 Git 中,记得在 .gitignore 里加上它):

    +
    DB_HOST=localhost
    +DB_USER=postgres
    +
    +
    +

    接下来就该创建 PostgreSQL 数据库实例并且将连接参数设置好了。创建数据库实例的命令通常是 createdb yourdbname,但不同平台可能有不同的方式,此教程里就不具体写了。

    +
    +

    小技巧

    +

    另外,您也可以使用 DB_DSN 来定义数据库连接参数,比如 postgresql://user:password@localhost:5432/dbname,它会覆盖出现在它前面的单个的配置,比如 DB_HOST

    +

    除了默认值不算之外,只要您定义了 DB_DSN ——不管是在环境变量中还是在 .env 文件中,它都比单个的连接参数有更高的优先级。比如哪怕环境变量中定义了 DB_HOST.env 文件中的 DB_DSN 仍然能够覆盖前者的值。

    +
    +../_images/gino-fastapi-models.svg

    然后创建一个 Python 的二级包 gino_fastapi_demo.models,用来封装数据库相关的代码。将下面的代码添加到 src/gino_fastapi_demo/models/__init__.py

    +
    from gino.ext.starlette import Gino
    +
    +from .. import config
    +
    +db = Gino(
    +    dsn=config.DB_DSN,
    +    pool_min_size=config.DB_POOL_MIN_SIZE,
    +    pool_max_size=config.DB_POOL_MAX_SIZE,
    +    echo=config.DB_ECHO,
    +    ssl=config.DB_SSL,
    +    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    +    retry_limit=config.DB_RETRY_LIMIT,
    +    retry_interval=config.DB_RETRY_INTERVAL,
    +)
    +
    +
    +

    最后,修改 src/gino_fastapi_demo/main.py,安装 GINO 扩展:

    +
     from fastapi import FastAPI
    ++
    ++from .models import db
    +
    + def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    ++    db.init_app(app)
    +     return app
    +
    +
    +

    保存该文件后,您应该可以看到 Uvicorn 服务器重载了我们的变更,然后连上了数据库:

    +
    WARNING:  Detected file change in 'src/gino_fastapi_demo/main.py'. Reloading...
    +INFO:     Shutting down
    +INFO:     Waiting for application shutdown.
    +INFO:     Application shutdown complete.
    +INFO:     Finished server process [63562]
    +INFO:     Started server process [63563]
    +INFO:     Waiting for application startup.
    +INFO:     Connecting to the database: postgresql://fantix:***@localhost
    +INFO:     Database connection pool created: <asyncpg.pool.Pool max=16 min=1 cur=1 use=0>
    +INFO:     Application startup complete.
    +
    +
    +
    +
    +

    创建 model 及 API

    +../_images/gino-fastapi-models-users.svg

    现在轮到实现 API 逻辑了。比方说我们打算做一个用户管理的服务,可以添加、查看和删除用户。

    +

    首先,我们需要一张数据库表 users,用于存储数据。在 gino_fastapi_demo.models.users 模块中添加一个映射这张表的 model User

    +
    from . import db
    +
    +class User(db.Model):
    +    __tablename__ = "users"
    +
    +    id = db.Column(db.BigInteger(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default="unnamed")
    +
    +
    +../_images/gino-fastapi-views.svg

    很简单的 model 定义,一切尽在不言中。

    +

    然后我们只需要在 API 的实现中正确使用它即可。创建一个新的 Python 二级包 gino_fastapi_demo.models.views,在其中添加一个模块 gino_fastapi_demo.views.users,内容为:

    +
    from fastapi import APIRouter
    +from pydantic import BaseModel
    +
    +from ..models.users import User
    +
    +router = APIRouter()
    +
    +@router.get("/users/{uid}")
    +async def get_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    return user.to_dict()
    +
    +class UserModel(BaseModel):
    +    name: str
    +
    +@router.post("/users")
    +async def add_user(user: UserModel):
    +    rv = await User.create(nickname=user.name)
    +    return rv.to_dict()
    +
    +@router.delete("/users/{uid}")
    +async def delete_user(uid: int):
    +    user = await User.get_or_404(uid)
    +    await user.delete()
    +    return dict(id=uid)
    +
    +def init_app(app):
    +    app.include_router(router)
    +
    +
    +

    APIRouter 用来收集新接口的定义,然后在 init_app 里集成到 FastAPI 应用中去。这里我们加一点反转控制 :把接口做成模块化的,用 Entry Points 功能进行拼装,避免需要手动一一 import 将来可能有的其他接口。将下面的代码添加到 gino_fastapi_demo.main

    +
    import logging
    +from importlib.metadata import entry_points
    +
    +logger = logging.getLogger(__name__)
    +
    +def load_modules(app=None):
    +    for ep in entry_points()["gino_fastapi_demo.modules"]:
    +        logger.info("Loading module: %s", ep.name)
    +        mod = ep.load()
    +        if app:
    +            init_app = getattr(mod, "init_app", None)
    +            if init_app:
    +                init_app(app)
    +
    +
    +
    +

    提示

    +

    如果您的 Python 版本低于 3.8,您还需要这个 importlib-metadata 的移植

    +
    +

    然后在我们的应用工厂函数中调用它:

    +
     def get_app():
    +     app = FastAPI(title="GINO FastAPI Demo")
    +     db.init_app(app)
    ++    load_modules(app)
    +     return app
    +
    +
    +

    最后,根据 Poetry 插件文档,在 pyproject.toml 文件中定义 Entry Point:

    +
    [tool.poetry.plugins."gino_fastapi_demo.modules"]
    +"users" = "gino_fastapi_demo.views.users"
    +
    +
    +

    再执行一次 poetry install 来激活这些 Entry Point——这次您可能需要亲自重启 Uvicorn 的开发服务器了,因为自动重载机制无法识别 pyproject.toml 文件的变更。

    +

    现在您应该可以在 Swagger UI 中看到那 3 个新接口了,但是它们还都不能用,因为我们还没有创建数据库表。

    +
    +
    +

    集成 Alembic

    +

    请在项目顶层文件夹中执行下面的命令,以开始使用 Alembic

    +
    $ poetry run alembic init migrations
    +
    +
    +../_images/gino-fastapi-alembic.svg

    这句命令会生产一个新的文件夹 migrations,包含了 Alembic 用于数据库表结构变更追踪的版本文件。同时创建的还有一个在顶层文件夹下面的 alembic.ini 文件,我们把这些文件都添加到 Git 中。

    +

    为了能让 Alembic 用上我们用 GINO 定义的 model,我们需要修改 migrations/env.py 文件去链接 GINO 实例:

    +
     # add your model's MetaData object here
    + # for 'autogenerate' support
    + # from myapp import mymodel
    + # target_metadata = mymodel.Base.metadata
    +-target_metadata = None
    ++from gino_fastapi_demo.config import DB_DSN
    ++from gino_fastapi_demo.main import db, load_modules
    ++
    ++load_modules()
    ++config.set_main_option("sqlalchemy.url", str(DB_DSN))
    ++target_metadata = db
    +
    +
    +

    然后就可以创建我们的第一个变更版本了:

    +
    $ poetry run alembic revision --autogenerate -m 'add users table'
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.autogenerate.compare] Detected added table 'users'
    +  Generating migrations/versions/32c0feba61ea_add_users_table.py ...  done
    +
    +
    +

    生成的版本文件大体上应该长这样:

    +
    def upgrade():
    +    op.create_table(
    +        "users",
    +        sa.Column("id", sa.BigInteger(), nullable=False),
    +        sa.Column("nickname", sa.Unicode(), nullable=True),
    +        sa.PrimaryKeyConstraint("id"),
    +    )
    +
    +def downgrade():
    +    op.drop_table("users")
    +
    +
    +
    +

    提示

    +

    以后需要再次修改数据库表结构的时候,您只需要修改 GINO model 然后执行 alembic revision --autogenerate 命令来生成对应改动的新版本即可。提交前记得看一下生成的版本文件,有时需要调整。

    +
    +

    我们终于可以应用此次变更了,执行下面的命令将数据库表结构版本升级至最高:

    +
    $ poetry run alembic upgrade head
    +INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
    +INFO  [alembic.runtime.migration] Will assume transactional DDL.
    +INFO  [alembic.runtime.migration] Running upgrade  -> 32c0feba61ea, add users table
    +
    +
    +

    到这里,所有的接口应该都可以正常工作了,您可以在 Swagger UI 中试一下。

    +
    +
    +

    编写测试

    +

    为了不影响开发环境的数据库,我们需要为测试创建单独的数据库。根据下面的补丁修改 gino_fastapi_demo.config

    +
     config = Config(".env")
    +
    ++TESTING = config("TESTING", cast=bool, default=False)
    +
    + DB_DRIVER = config("DB_DRIVER", default="postgresql")
    + DB_HOST = config("DB_HOST", default=None)
    + DB_PORT = config("DB_PORT", cast=int, default=None)
    + DB_USER = config("DB_USER", default=None)
    + DB_PASSWORD = config("DB_PASSWORD", cast=Secret, default=None)
    + DB_DATABASE = config("DB_DATABASE", default=None)
    ++if TESTING:
    ++    if DB_DATABASE:
    ++        DB_DATABASE += "_test"
    ++    else:
    ++        DB_DATABASE = "gino_fastapi_demo_test"
    + DB_DSN = config(
    +
    +
    +
    +

    提示

    +

    您需要执行 createdb 来创建数据库实例。比如说,如果您在 .env 文件中定义了 DB_DATABASE=mydb,那么测试数据库的名字就是 mydb_test。否则如果没定义的话,默认就是 gino_fastapi_demo_test

    +
    +

    然后在 tests/conftest.py 中创建 pytest fixture:

    +
    import pytest
    +from alembic.config import main
    +from starlette.config import environ
    +from starlette.testclient import TestClient
    +
    +environ["TESTING"] = "TRUE"
    +
    +@pytest.fixture
    +def client():
    +    from gino_fastapi_demo.main import db, get_app
    +
    +    main(["--raiseerr", "upgrade", "head"])
    +
    +    with TestClient(get_app()) as client:
    +        yield client
    +
    +    main(["--raiseerr", "downgrade", "base"])
    +
    +
    +../_images/gino-fastapi-tests.svg

    这个 fixture 的作用是,在跑测试之前创建所有的数据库表、提供一个 StarletteTestClient、并且在测试跑完之后删除所有的表及其数据,为后续测试保持一个干净的环境。

    +

    下面是一个简单的测试例子,tests/test_users.py

    +
    import uuid
    +
    +def test_crud(client):
    +    # create
    +    nickname = str(uuid.uuid4())
    +    r = client.post("/users", json=dict(name=nickname))
    +    r.raise_for_status()
    +
    +    # retrieve
    +    url = f"/users/{r.json()['id']}"
    +    assert client.get(url).json()["nickname"] == nickname
    +
    +    # delete
    +    client.delete(url).raise_for_status()
    +    assert client.get(url).status_code == 404
    +
    +
    +

    测试跑起来:

    +
    $ poetry run pytest
    +=========================== test session starts ===========================
    +platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
    +rootdir: gino-fastapi-demo
    +collected 1 item
    +
    +tests/test_users.py .                                               [100%]
    +
    +============================ 1 passed in 1.21s ============================
    +
    +
    +
    +
    +

    生产环境注意事项

    +

    最近 Docker/Kubernetes 挺火,我们也写一个 Dockerfile

    +
    FROM python:3.8-alpine as base
    +
    +FROM base as builder
    +RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev make postgresql-dev
    +RUN pip install poetry
    +COPY . /src/
    +WORKDIR /src
    +RUN python -m venv /env && . /env/bin/activate && poetry install
    +
    +FROM base
    +RUN apk add --no-cache postgresql-libs
    +COPY --from=builder /env /env
    +COPY --from=builder /src /src
    +WORKDIR /src
    +CMD ["/env/bin/gunicorn", "gino_fastapi_demo.asgi:app", "-b", "0.0.0.0:80", "-k", "uvicorn.workers.UvicornWorker"]
    +
    +
    +

    这个 Dockerfile 里,为了降低目标镜像文件的大小,我们分成了两步来分别进行源码构建和生产镜像的组装。另外,我们还采用了 Gunicorn 搭配 UvicornUvicornWorker 的方式来获取最佳生产级别可靠性。

    +

    回头看一下项目里一共有哪些文件。

    +../_images/gino-fastapi-layout.svg

    至此,我们就完成了演示项目的开发。下面是上生产可以用到的一个不完整检查清单:

    +
      +
    • DB_RETRY_LIMIT 设置成一个稍微大一点的数字,以支持在数据库就绪前启动应用服务器的情况。

    • +
    • migrations/env.py 中实现同样的重连尝试逻辑,这样 Alembic 也能拥有同样的特性。

    • +
    • 如果需要的话,启用 DB_SSL

    • +
    • 写一个 docker-compose.yml,用于其他开发人员快速尝鲜,甚至可以用于开发。

    • +
    • 启用持续集成 <CI_>,安装 pytest-cov 并且用 --cov-fail-under 参数来保障测试覆盖率。

    • +
    • 集成静态代码检查工具和安全性/CVE筛查工具。

    • +
    • 正确自动化 Alembic 的升级流程,比如在每次新版本部署之后执行。

    • +
    • 注意针对诸如 CSRFXSS 等常见攻击的安全性防护。

    • +
    • 编写压力测试。

    • +
    +

    最后再贴一次,实例程序的源码在这里,本教程的文档源码在`这里<https://github.com/python-gino/gino/blob/master/docs/tutorials/fastapi.rst>`__,请敞开了提 PR,修问题或者分享想法都行。祝玩得愉快!

    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/zh/master/tutorials/tutorial.html b/docs/zh/master/tutorials/tutorial.html new file mode 100644 index 0000000..ddd0e57 --- /dev/null +++ b/docs/zh/master/tutorials/tutorial.html @@ -0,0 +1,493 @@ + + + + + + + + GINO 基础教程 - GINO 1.1.0rc1 文档 + + + + + + + + + + + +
    + + + +
    + +
    +
    +
    +
    + +
    +

    GINO 基础教程

    +

    这是一篇写给刚入坑同学的指南,将介绍 GINO 的基本部分。阅读之前,请先了解以下知识点:

    + +

    您不需要对 SQLAlchemy 有所了解。

    +
    +

    介绍

    +

    简单来说,GINO 可以在您的异步应用中帮助您完成 SQL 语句的生成及执行,您只需要通过友好的对象化 API 来操作您的数据即可,无需亲自编写 SQL 与数据库交互。

    +

    因为异步编程并不会使您的程序变快——如果说不拖累的话——而且还会增加复杂度和风险,所以也许您并不需要 GINO 或者说是异步数据库连接。跳坑之前请先阅读为什么要用异步 ORM?

    +
    +
    +

    安装

    +

    请在终端中执行以下命令以安装 GINO:

    +
    $ pip install gino
    +
    +
    +

    以上就是安装 GINO 的推荐方式,因为这种方式始终会去安装最新的稳定版。

    +

    如果您还没有安装过 pip,您可以参阅 Python 安装指南

    +

    另外如果您在使用 Poetry 进行项目依赖关系管理,那需要执行的则是:

    +
    $ poetry add gino
    +
    +
    +
    +
    +

    声明模型

    +

    开始之前,我们需要先创建一个 Gino 的全局实例,通常叫做 db

    +
    from gino import Gino
    +
    +db = Gino()
    +
    +
    +

    db 可以被当做是数据库的一个代表,后续大部分的数据库交互都将通过它来完成。

    +

    “Model” 是 GINO 中的一个基本概念,它表示继承自 db.Model 的用户定义类。每个 Model 的子类代表了数据库中的一张表,而这些类的对象则代表了对应表中的一行数据。如果您曾经使用过其它 ORM 产品,对这种映射关系应该不感到陌生。现在我们尝试定义一个 model:

    +
    class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +

    这里的 User 类其实就是在定义一张叫做 users 的数据库表,包含了 idnickname 两个字段。请注意,__tablename__ 是一个必要的固定属性。GINO 建议使用单数名词来为 model 命名,同时使用复数名词去命名表。每个 db.Column 属性都定义了一个数据库字段,其中第一个参数是字段类型,其余参数则用来定义字段其他属性或约束。您可以参考 SQLAlchemy 的文档来了解不同 db 类型到数据库类型的对应关系。

    +
    +

    注解

    +

    SQLAlchemy 是 Python 中一个强大的非异步 ORM 库,而 GINO 就是基于其构建的。通过不同的 SQL 方言实现,SQLAlchemy 支持包括 PostgreSQL 和 MySQL 在内的许多流行的 RDBMS,以至于有时相同的 Python 代码可以不经修改地运行在不同的数据库上。GINO 自然也承袭了这一特性,但目前暂仅支持 PostgreSQL(通过 asyncpg)。

    +
    +

    如果需要定义涵盖多个列的数据库约束或索引,您仍然可以通过 model 类属性的方式来定义,属性名称虽未被用到,但不能重复。例如:

    +
    class Booking(db.Model):
    +    __tablename__ = 'bookings'
    +
    +   day = db.Column(db.Date)
    +   booker = db.Column(db.String)
    +   room = db.Column(db.String)
    +
    +   _pk = db.PrimaryKeyConstraint('day', 'booker', name='bookings_pkey')
    +   _idx1 = db.Index('bookings_idx_day_room', 'day', 'room', unique=True)
    +   _idx2 = db.Index('bookings_idx_booker_room', 'booker', 'room')
    +
    +
    +

    另外如果有倾向性,您也可以在 model 类之外定义约束和索引,请参考 SQLAlchemy 文档来了解更多细节。

    +

    由于一些限制,目前不允许在父类中直接使用类属性的方式来单独定义数据库约束和索引,__table_args__ 也是一样的。GINO 提供了 declared_attr() 来实现比如 mixin 类这样的功能,更多信息请参阅其 API 文档。

    +
    +
    +

    建立连接

    +

    前面的声明只是定义了映射关系,并非实际在数据库中创建了这些表结构。为了使用 GINO 来创建表,我们需要先与数据库建立连接。这里我们先为本指南创建一个 PostgreSQL 的数据库实例:

    +
    $ createdb gino
    +
    +
    +

    然后,告诉我们的 db 对象去连接这个数据库:

    +
    import asyncio
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +

    如果执行成功了,那就意味着您连上了新创建的数据库。此处的 postgresql 代表了要用的数据库方言(默认的驱动是 asyncpg,您也可以显式地指定使用它:postgresql+asyncpg:// 或者就只写 asyncpg://),localhost 是数据库服务器所在的地址,gino 是数据库实例的名字。这里可以读到更多关于如何构造一个数据库 URL 的信息。

    +
    +

    注解

    +

    在底层,set_bind() 调用了 create_engine() 来创建 engine,并将其绑定到 db 对象上。GINO engine 与 SQLAlchemy engine 类似,但 GINO engine 是异步的,而后者是阻塞式的。关于如何使用 engine,请参考 GINO 的 API 文档。

    +
    +

    建立连接之后,我们就可以用 GINO 在数据库中创建我们的表了(在同一个 main() 函数里):

    +
    await db.gino.create_all()
    +
    +
    +
    +

    警告

    +

    这里是 db.gino.create_all,而不是 db.create_all,因为 db 继承自 SQLAlchemy 的 MetaData,而 db.create_all 是 SQLAlchemy 的阻塞式方法,无法适用于绑定的 GINO engine。

    +

    实践中 create_all() 通常并不是一个理想的解决方案。为了管理数据库表结构,我们通常推荐使用诸如 Alembic 这样的工具,请参阅如何 使用 Alembic

    +
    +

    如果您想显式地断开与数据库的连接,您可以这么做:

    +
    await db.pop_bind().close()
    +
    +
    +

    继续之前,让我们重新看一下前面所有的代码:

    +
    import asyncio
    +from gino import Gino
    +
    +db = Gino()
    +
    +
    +class User(db.Model):
    +    __tablename__ = 'users'
    +
    +    id = db.Column(db.Integer(), primary_key=True)
    +    nickname = db.Column(db.Unicode(), default='noname')
    +
    +
    +async def main():
    +    await db.set_bind('postgresql://localhost/gino')
    +    await db.gino.create_all()
    +
    +    # further code goes here
    +
    +    await db.pop_bind().close()
    +
    +
    +asyncio.get_event_loop().run_until_complete(main())
    +
    +
    +
    +
    +

    增删改查

    +

    为了操作数据库中的数据,GINO 提供了基本的基于对象的增删改查功能。

    +
    +

    +

    让我们从创建一个 User 对象开始:

    +
    user = await User.create(nickname='fantix')
    +# This will cause GINO to execute this SQL with parameter 'fantix':
    +# INSERT INTO users (nickname) VALUES ($1) RETURNING users.id, users.nickname
    +
    +
    +

    正如之前所说,user 对象代表了数据库中新插入的这一行数据。您可以通过 user 对象上的之前定义的列属性来访问每一列的值:

    +
    print(f'ID:       {user.id}')           # 1
    +print(f'Nickname: {user.nickname}')     # fantix
    +
    +
    +

    另外,您也可以先在内存中创建一个 user 对象,然后再将其插入到数据库中:

    +
    user = User(nickname='fantix')
    +user.nickname += ' (founder)'
    +await user.create()
    +
    +
    +
    +
    +

    +

    想要通过主键来获取一个 model 对象,您可以使用 model 的类方法 get()。比如,重新获取刚才插入的同一行数据:

    +
    user = await User.get(1)
    +# SQL (parameter: 1):
    +# SELECT users.id, users.nickname FROM users WHERE users.id = $1
    +
    +
    +

    常规的 SQL 查询则是通过类属性 query 来完成。比如,获取数据库中所有的 User 对象的列表:

    +
    all_users = await db.all(User.query)
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +

    或者,您也可以使用 querygino 扩展。比如,下面的代码可以实现一样的效果:

    +
    all_users = await User.query.gino.all()
    +# SQL:
    +# SELECT users.id, users.nickname FROM users
    +
    +
    +
    +

    注解

    +

    实际上,User.query 是一个普通的 SQLAlchemy 查询对象,SQLAlchemy 的阻塞式执行方法依然存在其上,因此 GINO 向所有 SQLAlchemy 的“Executable”对象注入了一个 gino 扩展,以便在不影响 SQLAlchemy 原有 API 的基础上,让直接异步地执行这些查询对象更容易,而不用每次都通过 engine 或 db 对象来执行。

    +
    +

    现在让我们尝试增加一些过滤器。比如,查找出所有 ID 小于 10 的用户:

    +
    founding_users = await User.query.where(User.id < 10).gino.all()
    +# SQL (parameter: 10):
    +# SELECT users.id, users.nickname FROM users WHERE users.id < $1
    +
    +
    +

    因为查询对象就是出自于 SQLAlchemy core,所以请参阅如何编写查询

    +
    +

    警告

    +

    当您拿到一个 model 对象时,这个对象就已经彻底与数据库分离了,完全成为内存中的一个普通对象。这就意味着,即使数据库中对应的行发生了变化,对象的值仍然不会受到丝毫影响。类似地,如果您修改了该对象的值,数据库也不会受到任何影响。

    +

    并且,GINO 也不会追踪 model 对象,因此重复查询同一行数据将会得到两个独立的、拥有相同值的对象,修改其中一个的值不会幽灵般地影响到另一个的值。

    +

    不同于传统 ORM 的 model 对象通常是有状态的,GINO 的 model 对象则更像是用对象封装的 SQL 查询结果,这是 GINO 为了适应异步编程而特意设计的简易性,也是“GINO 不是 ORM”名字的来源。

    +
    +

    有时我们仅需要获取一个对象,比如验证登录时,使用用户名来查找一个用户。这时,可以使用这种便捷的写法:

    +
    user = await User.query.where(User.nickname == 'fantix').gino.first()
    +# SQL (parameter: 'fantix'):
    +# SELECT users.id, users.nickname FROM users WHERE users.nickname = $1
    +
    +
    +

    如果数据库中没有叫“fantix”的用户,则 user 会被置为 None

    +

    又有时,我们会需要获取一个单独的值,比如 ID 为 1 的用户的名字。此时可以使用 model 的类方法 select()

    +
    name = await User.select('nickname').where(User.id == 1).gino.scalar()
    +# SQL (parameter: 1):
    +# SELECT users.nickname FROM users WHERE users.id = $1
    +print(name)  # fantix
    +
    +
    +

    又比如,查询用户数量:

    +
    population = await db.func.count(User.id).gino.scalar()
    +# SQL:
    +# SELECT count(users.id) AS count_1 FROM users
    +print(population)  # 17 for example
    +
    +
    +
    +
    +

    +

    接下来,让我们尝试对数据做一些修改,下面的例子会穿插一些前面用过的查询操作。

    +
    # create a new user
    +user = await User.create(nickname='fantix')
    +
    +# get its name
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +assert name == user.nickname  # they are both 'fantix' before the update
    +
    +# modification here
    +await user.update(nickname='daisy').apply()
    +# SQL (parameters: 'daisy', 1):
    +# UPDATE users SET nickname=$1 WHERE users.id = $2 RETURNING users.nickname
    +print(user.nickname)  # daisy
    +
    +# get its name again
    +name = await User.select('nickname').where(
    +    User.id == user.id).gino.scalar()
    +print(name)  # daisy
    +assert name == user.nickname  # they are both 'daisy' after the update
    +
    +
    +

    这里的 update() 是我们碰到的第一个 model 实例上的 GINO 方法,它接受多个自定义命名参数,参数名对应着 model 的字段名,而参数值则为期望修改成的新的值。连着写的 apply() 则会将这一修改同步到数据库中。

    +
    +

    注解

    +

    GINO 显式地将“修改内存中对象的值”与“修改数据库中的行”拆分成了两个方法: update()apply()update() 负责修改内存中的值,并且将改动记录在返回的 UpdateRequest 对象中;紧接着调用的 UpdateRequest 对象的 apply() 方法则会将这些记录下的改动通过 SQL 更新到数据库中。

    +
    +
    +

    小技巧

    +

    UpdateRequest 对象还有一个方法也叫 update(),它与 model 对象上的 update() 方法的功能是一样的,只不过前者还会将新的改动记录与当前 UpdateRequest 已记录的改动合并在一起,并且返回同一个 UpdateRequest 对象。这意味着,您可以连着写多个 update() 调用,最后用一个 apply() 结尾,或者仅仅是通过 UpdateRequest 对象来完成内存对象的多次改动。

    +
    +

    Model 对象上的 update() 方法只能操作该对象对应的数据库中的一行数据,而如果您想要批量更新多行数据的话,您可以使用 model 类上的 update() 类方法。用法略有不同:

    +
    await User.update.values(nickname='Founding Member ' + User.nickname).where(
    +    User.id < 10).gino.status()
    +# SQL (parameter: 'Founding Member ', 10):
    +# UPDATE users SET nickname=($1 || users.nickname) WHERE users.id < $2
    +
    +name = await User.select('nickname').where(
    +    User.id == 1).gino.scalar()
    +print(name)  # Founding Member fantix
    +
    +
    +

    这里不再有 UpdateRequest 了,所有的操作又回到了普通的 SQLAlchemy 用法,更多细节可以参考 SQLAlchemy 的文档

    +
    +
    +

    +

    最后,删除一行数据与更新一行数据有些类似,但要简单很多:

    +
    user = await User.create(nickname='fantix')
    +await user.delete()
    +# SQL (parameter: 1):
    +# DELETE FROM users WHERE users.id = $1
    +print(await User.get(user.id))  # None
    +
    +
    +
    +

    提示

    +

    还记得内存对象的事情吗?在最后一行的 print() 中,尽管数据库中已经没有这一行数据了,但是 user 对象依然在内存中,它的值也都没有变化,所以这里仍然可以用 user.id

    +
    +

    或者批量删除(千万不要忘记写 where!是不是整个表都不想要了?):

    +
    await User.delete.where(User.id > 10).gino.status()
    +# SQL (parameter: 10):
    +# DELETE FROM users WHERE users.id > $1
    +
    +
    +

    有了基本的 增删改查,您应该已经可以用 GINO 做出一些不可思议的东西来了。这篇上手指南到此结束,要了解更多请继续阅读文档的剩余部分。祝编程愉快!

    +
    +
    +
    + + +
    +

    + + 知识共享许可协议 + +
    + 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 +

    +
    + +
    +
    + + + + +
    + +
    + + +
    + + 您正在浏览 master 分支,该文档可能涉及尚未发布的功能。 + +
    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..07b38bc Binary files /dev/null and b/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..9cbbb07 --- /dev/null +++ b/index.html @@ -0,0 +1,47 @@ + + + + python-gino.org + + +
    GINO is a non-typical
    object-relational mapping
    library in Python for asyncio.
    GitHub Project

    asyncpg

    High performance
    PostgreSQL driver

    SQLAlchemy

    Robust & mature SQL
    rendering engine

    No Surprise

    Lightweight & explicit
    architecture

    Being Pythonic

    Practical API for
    development performance

     HIGHLIGHTS

    + GINO is an engineering product,
    + built on top of popular tools,
    + tailored for asyncio.

    Read More

    ARCHITECTURE 

    + Query Builder +

    + For applications using asyncpg directly,
    + GINO adds the ingredient of building SQLs
    + conveniently with SQLAlchemy core. +

    + Async SQLAlchemy +

    + GINO provides SQLAlchemy-like Engine
    + with async APIs. +

    + Object Loader +

    + GINO could load data into POPOs
    + (Plain Old Python Objects) for
    + easier data access. +

    Read More
    SQLAlchemy core
    GINO Async Wrapper
    GINO Loader
    GINO Dialect
    asyncpg

    SHOWCASE

    + I built GINO because I found it difficult to access database in Python with + asyncio in 2017, and I wanted all three of explicitness, development performance + and runtime performance in a single library. +

    + Following GNU’s naming style, GINO is recursively defined as GINO Is Not ORM, + indicating that it’s almost an ORM but refuses to inherit the implicit behaviors + found in typical ORMs. +

    ROADMAP

    1.0 Release

    Redo packaging and automation


    Embed aiocontextvars and move extensions out


    python-gino.org


    Release candidates


    Better documentation


    Blog post


    Cached prepared statement


    MySQL support


    Better typing support


    SQLAlchemy 1.4 SQLAlchemy 2 support


    + + diff --git a/layouts/README.md b/layouts/README.md deleted file mode 100644 index cad1ad5..0000000 --- a/layouts/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# LAYOUTS - -**This directory is not required, you can delete it if you don't want to use it.** - -This directory contains your Application Layouts. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). diff --git a/layouts/default.vue b/layouts/default.vue deleted file mode 100644 index d7e1e07..0000000 --- a/layouts/default.vue +++ /dev/null @@ -1,456 +0,0 @@ - - - - - diff --git a/middleware/README.md b/middleware/README.md deleted file mode 100644 index 01595de..0000000 --- a/middleware/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# MIDDLEWARE - -**This directory is not required, you can delete it if you don't want to use it.** - -This directory contains your application middleware. -Middleware let you define custom functions that can be run before rendering either a page or a group of pages. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). diff --git a/nuxt.config.js b/nuxt.config.js deleted file mode 100644 index c40c9a2..0000000 --- a/nuxt.config.js +++ /dev/null @@ -1,94 +0,0 @@ -export default { - mode: 'universal', - /* - ** Headers of the page - */ - head: { - title: process.env.npm_package_name || '', - meta: [ - {charset: 'utf-8'}, - {name: 'viewport', content: 'width=device-width, initial-scale=1'}, - { - hid: 'description', - name: 'description', - content: process.env.npm_package_description || '' - } - ], - link: [ - {rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}, - { - rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css?family=Raleway:400,500,600,700&display=swap' - } - ] - }, - /* - ** Customize the progress-bar color - */ - loading: {color: '#fff'}, - /* - ** Global CSS - */ - css: [ - '@/assets/polarbear.scss' - ], - /* - ** Plugins to load before mounting the App - */ - plugins: [ - '~/plugins/highlight' - ], - /* - ** Nuxt.js dev-modules - */ - buildModules: [], - /* - ** Nuxt.js modules - */ - modules: [ - // Doc: https://axios.nuxtjs.org/usage - '@nuxtjs/axios', - '@nuxtjs/pwa', - '@nuxtjs/google-gtag', - ], - /* - ** Axios module configuration - ** See https://axios.nuxtjs.org/options - */ - axios: {}, - /* - ** Build configuration - */ - build: { - /* - ** You can extend webpack config here - */ - extend (config, {loaders}) { - loaders.imgUrl.limit = 16384 - config.module.rules.push({ - test: /\.py$/i, - use: 'raw-loader' - }) - } - }, - /* - ** Google gtag - */ - 'google-gtag': { - id: 'UA-3759436-10', - config: { - anonymize_ip: true, // anonymize IP - send_page_view: false, // might be necessary to avoid duplicated page track on page reload - }, - debug: false, // enable to track in dev mode - disableAutoPageTrack: false, // disable if you don't want to track each page route with router.afterEach(...). - }, - /* - ** PWA - */ - pwa: { - icon: { - iconSrc: 'assets/logo-light.svg' - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 28bf0fc..0000000 --- a/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "python-gino.org", - "version": "1.0.0", - "description": "python-gino.org", - "author": "Fantix King", - "private": true, - "scripts": { - "dev": "nuxt", - "build": "nuxt build", - "start": "nuxt start", - "generate": "nuxt generate" - }, - "dependencies": { - "@nuxtjs/axios": "^5.3.6", - "@nuxtjs/google-gtag": "^1.0.4", - "@nuxtjs/pwa": "https://github.com/fantix/pwa-module", - "nuxt": "^2.0.0", - "vue-highlightjs": "^1.3.3" - }, - "devDependencies": { - "node-sass": "^4.13.1", - "raw-loader": "^4.0.0", - "sass-loader": "^8.0.2" - } -} diff --git a/pages/README.md b/pages/README.md deleted file mode 100644 index 1d5d48b..0000000 --- a/pages/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# PAGES - -This directory contains your Application Views and Routes. -The framework reads all the `*.vue` files inside this directory and creates the router of your application. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). diff --git a/pages/credits.vue b/pages/credits.vue deleted file mode 100644 index e62a3b4..0000000 --- a/pages/credits.vue +++ /dev/null @@ -1,356 +0,0 @@ - - - - - diff --git a/pages/index.vue b/pages/index.vue deleted file mode 100644 index da72110..0000000 --- a/pages/index.vue +++ /dev/null @@ -1,78 +0,0 @@ - - - - - diff --git a/plugins/README.md b/plugins/README.md deleted file mode 100644 index ca1f9d8..0000000 --- a/plugins/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# PLUGINS - -**This directory is not required, you can delete it if you don't want to use it.** - -This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). diff --git a/plugins/highlight.js b/plugins/highlight.js deleted file mode 100644 index 42f2927..0000000 --- a/plugins/highlight.js +++ /dev/null @@ -1,6 +0,0 @@ -// Import Vue and vue-highlgihtjs -import Vue from 'vue' -import VueHighlightJS from 'vue-highlightjs' - -// Tell Vue.js to use vue-highlightjs -Vue.use(VueHighlightJS) diff --git a/store/README.md b/store/README.md deleted file mode 100644 index 1972d27..0000000 --- a/store/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# STORE - -**This directory is not required, you can delete it if you don't want to use it.** - -This directory contains your Vuex Store files. -Vuex Store option is implemented in the Nuxt.js framework. - -Creating a file in this directory automatically activates the option in the framework. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..3f26afe --- /dev/null +++ b/sw.js @@ -0,0 +1,32 @@ +importScripts('https://cdn.jsdelivr.net/npm/workbox-cdn@4.3.1/workbox/workbox-sw.js') + +// -------------------------------------------------- +// Configure +// -------------------------------------------------- + +// Set workbox config +workbox.setConfig({ + "debug": false +}) + +// Start controlling any existing clients as soon as it activates +workbox.core.clientsClaim() + +// Skip over the SW waiting lifecycle stage +workbox.core.skipWaiting() + +workbox.precaching.cleanupOutdatedCaches() + +// -------------------------------------------------- +// Precaches +// -------------------------------------------------- + +// Precache assets + +// -------------------------------------------------- +// Runtime Caching +// -------------------------------------------------- + +// Register route handlers for runtimeCaching +workbox.routing.registerRoute(new RegExp('/_nuxt/'), new workbox.strategies.CacheFirst ({}), 'GET') +workbox.routing.registerRoute(new RegExp('/'), new workbox.strategies.NetworkFirst ({}), 'GET') diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 2a60d6e..0000000 --- a/yarn.lock +++ /dev/null @@ -1,8467 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.8.3.tgz?cache=0&sync_timestamp=1578951720714&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha1-M+JZA9dIEYFTThLsCiXxa2/PQZ4= - dependencies: - "@babel/highlight" "^7.8.3" - -"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/compat-data/download/@babel/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha1-BIFVVvyQsMF0q9LAwbuWb6oDamw= - dependencies: - browserslist "^4.9.1" - invariant "^2.2.4" - semver "^5.5.0" - -"@babel/core@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/core/download/@babel/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha1-rJd7U4t34TL/cG87ik260JwDxW4= - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@^7.9.0", "@babel/generator@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/generator/download/@babel/generator-7.9.5.tgz?cache=0&sync_timestamp=1586287809200&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fgenerator%2Fdownload%2F%40babel%2Fgenerator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" - integrity sha1-J/CRd0GsxB5uqs7W1o+Ww/qa+vk= - dependencies: - "@babel/types" "^7.9.5" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/helper-annotate-as-pure@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-annotate-as-pure/download/@babel/helper-annotate-as-pure-7.8.3.tgz?cache=0&sync_timestamp=1578951725907&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-annotate-as-pure%2Fdownload%2F%40babel%2Fhelper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" - integrity sha1-YLwLxlf2Ogkk/5pLSgskoTz03u4= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-builder-binary-assignment-operator-visitor/download/@babel/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz?cache=0&sync_timestamp=1578951889069&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-builder-binary-assignment-operator-visitor%2Fdownload%2F%40babel%2Fhelper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" - integrity sha1-yECXpCegYaxWocMOv1S3si0kFQM= - dependencies: - "@babel/helper-explode-assignable-expression" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.npm.taobao.org/@babel/helper-compilation-targets/download/@babel/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha1-2sHuoVnA5L1G4wm1obBKZrU8Hd4= - dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/helper-create-class-features-plugin@^7.8.3": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/helper-create-class-features-plugin/download/@babel/helper-create-class-features-plugin-7.9.5.tgz#79753d44017806b481017f24b02fd4113c7106ea" - integrity sha1-eXU9RAF4BrSBAX8ksC/UETxxBuo= - dependencies: - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - -"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": - version "7.8.8" - resolved "https://registry.npm.taobao.org/@babel/helper-create-regexp-features-plugin/download/@babel/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" - integrity sha1-XYQYC1iPVgt4ZO+u6okkPlgxIIc= - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-regex" "^7.8.3" - regexpu-core "^4.7.0" - -"@babel/helper-define-map@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-define-map/download/@babel/helper-define-map-7.8.3.tgz?cache=0&sync_timestamp=1578951889698&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-define-map%2Fdownload%2F%40babel%2Fhelper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" - integrity sha1-oGVcrVRRw3YLcm66h18c2PqgLBU= - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/types" "^7.8.3" - lodash "^4.17.13" - -"@babel/helper-explode-assignable-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-explode-assignable-expression/download/@babel/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" - integrity sha1-pyjcW06J4w/C38fQT6KKkwZT+YI= - dependencies: - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/helper-function-name/download/@babel/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" - integrity sha1-K1OCDTUnUSDhh0qC5aq+E3aSClw= - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.9.5" - -"@babel/helper-get-function-arity@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-get-function-arity/download/@babel/helper-get-function-arity-7.8.3.tgz?cache=0&sync_timestamp=1578951726794&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-get-function-arity%2Fdownload%2F%40babel%2Fhelper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" - integrity sha1-uJS5R70AQ4HOY+odufCFR+kgq9U= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-hoist-variables@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-hoist-variables/download/@babel/helper-hoist-variables-7.8.3.tgz?cache=0&sync_timestamp=1578951735732&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-hoist-variables%2Fdownload%2F%40babel%2Fhelper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" - integrity sha1-Hb6ba1XXjJtBg/yM3G4wzrg7cTQ= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-member-expression-to-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-member-expression-to-functions/download/@babel/helper-member-expression-to-functions-7.8.3.tgz?cache=0&sync_timestamp=1578951730732&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-member-expression-to-functions%2Fdownload%2F%40babel%2Fhelper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" - integrity sha1-ZZtxBJjqbB2ZB+DHPyBu7n2twkw= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-module-imports/download/@babel/helper-module-imports-7.8.3.tgz?cache=0&sync_timestamp=1578951735889&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-module-imports%2Fdownload%2F%40babel%2Fhelper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" - integrity sha1-f+OVibOcAWMxtrjD9EHo8LFBlJg= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-transforms@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/helper-module-transforms/download/@babel/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" - integrity sha1-Q7NN/hWWGRhwfSRzJ0MTiOn+luU= - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.9.0" - lodash "^4.17.13" - -"@babel/helper-optimise-call-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-optimise-call-expression/download/@babel/helper-optimise-call-expression-7.8.3.tgz?cache=0&sync_timestamp=1578951725829&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-optimise-call-expression%2Fdownload%2F%40babel%2Fhelper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" - integrity sha1-ftBxgT0Jx1KY708giVYAa2ER7Lk= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-plugin-utils/download/@babel/helper-plugin-utils-7.8.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-plugin-utils%2Fdownload%2F%40babel%2Fhelper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" - integrity sha1-nqKTvhm6vA9S/4yoizTDYRsghnA= - -"@babel/helper-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-regex/download/@babel/helper-regex-7.8.3.tgz?cache=0&sync_timestamp=1578951752963&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-regex%2Fdownload%2F%40babel%2Fhelper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" - integrity sha1-E5dyYH1RuT8j7/5yEFsxnSpMaWU= - dependencies: - lodash "^4.17.13" - -"@babel/helper-remap-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-remap-async-to-generator/download/@babel/helper-remap-async-to-generator-7.8.3.tgz?cache=0&sync_timestamp=1578951891919&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-remap-async-to-generator%2Fdownload%2F%40babel%2Fhelper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" - integrity sha1-JzxgDYub9QBhQsHjWIfVVcEu3YY= - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-wrap-function" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.npm.taobao.org/@babel/helper-replace-supers/download/@babel/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha1-Wtp0T9WtcyA78dZ0WaJ9y6Z+/8g= - dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/helper-simple-access@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-simple-access/download/@babel/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" - integrity sha1-f4EJkotNq0ZUB2mGr1dSMd62Oa4= - dependencies: - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-split-export-declaration@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-split-export-declaration/download/@babel/helper-split-export-declaration-7.8.3.tgz?cache=0&sync_timestamp=1578951740752&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-split-export-declaration%2Fdownload%2F%40babel%2Fhelper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" - integrity sha1-ManzAHD5E2inGCzwX4MXgQZfx6k= - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/helper-validator-identifier/download/@babel/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" - integrity sha1-kJd6jm+/a0MafcMXUu7iM78FLYA= - -"@babel/helper-wrap-function@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/helper-wrap-function/download/@babel/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" - integrity sha1-nb2yu1XvFKqgH+jJm2Kb1TUthhA= - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.npm.taobao.org/@babel/helpers/download/@babel/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha1-tCqBqBHx5zE7iMuorcZrPZrmwJ8= - dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - -"@babel/highlight@^7.8.3": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" - integrity sha1-TptFzLgreWBycbKXmtgse2gWMHk= - dependencies: - "@babel/helper-validator-identifier" "^7.9.0" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.8.6", "@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.npm.taobao.org/@babel/parser/download/@babel/parser-7.9.4.tgz?cache=0&sync_timestamp=1585040110633&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha1-aKNeawMZu8AURlvkOCgwARPy8ug= - -"@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-async-generator-functions/download/@babel/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" - integrity sha1-utMpxnCzgliXIbJ1QMfSiGAcbm8= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-class-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-class-properties/download/@babel/plugin-proposal-class-properties-7.8.3.tgz?cache=0&sync_timestamp=1578951971768&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-class-properties%2Fdownload%2F%40babel%2Fplugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" - integrity sha1-XgZlSvXNBLYIkVqtqbKmeIAERk4= - dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-proposal-decorators@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-decorators/download/@babel/plugin-proposal-decorators-7.8.3.tgz?cache=0&sync_timestamp=1578951972315&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-decorators%2Fdownload%2F%40babel%2Fplugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" - integrity sha1-IVaGCrZcWr8GjD9nBCGEBBBmVD4= - dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-decorators" "^7.8.3" - -"@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-dynamic-import/download/@babel/plugin-proposal-dynamic-import-7.8.3.tgz?cache=0&sync_timestamp=1578978915050&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-dynamic-import%2Fdownload%2F%40babel%2Fplugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" - integrity sha1-OMT+VVdEgm6X4q6TCw+0zAfmYFQ= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-json-strings/download/@babel/plugin-proposal-json-strings-7.8.3.tgz?cache=0&sync_timestamp=1578978920434&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-json-strings%2Fdownload%2F%40babel%2Fplugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" - integrity sha1-2lIWsjipi1ih4F1oUhBLEPmnDWs= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-nullish-coalescing-operator/download/@babel/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz?cache=0&sync_timestamp=1578978916382&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-nullish-coalescing-operator%2Fdownload%2F%40babel%2Fplugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha1-5FciU/3u1lzd7s/as/kor+sv1dI= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-numeric-separator/download/@babel/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" - integrity sha1-XWdpQJaZ7Js7aGhM2BFs7f+Tutg= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - -"@babel/plugin-proposal-object-rest-spread@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-object-rest-spread/download/@babel/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" - integrity sha1-P9ZZETBth0YBTsDQz3jw45oUkRY= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.9.5" - -"@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-optional-catch-binding/download/@babel/plugin-proposal-optional-catch-binding-7.8.3.tgz?cache=0&sync_timestamp=1578978918901&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-optional-catch-binding%2Fdownload%2F%40babel%2Fplugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" - integrity sha1-ne6WqxZQ7tiGRq6XNMoWesSpxck= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-optional-chaining/download/@babel/plugin-proposal-optional-chaining-7.9.0.tgz?cache=0&sync_timestamp=1584718601089&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-optional-chaining%2Fdownload%2F%40babel%2Fplugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha1-MdsWsVTDnWuKZFKSRyuYOUwpKlg= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": - version "7.8.8" - resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-unicode-property-regex/download/@babel/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" - integrity sha1-7jqV6QzcBP6M2S7DJ5+gF9aKDR0= - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.8" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-async-generators@^7.8.0": - version "7.8.4" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-async-generators/download/@babel/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha1-qYP7Gusuw/btBCohD2QOkOeG/g0= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-decorators@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-decorators/download/@babel/plugin-syntax-decorators-7.8.3.tgz?cache=0&sync_timestamp=1578978921443&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-decorators%2Fdownload%2F%40babel%2Fplugin-syntax-decorators-7.8.3.tgz#8d2c15a9f1af624b0025f961682a9d53d3001bda" - integrity sha1-jSwVqfGvYksAJflhaCqdU9MAG9o= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-dynamic-import@^7.8.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-dynamic-import/download/@babel/plugin-syntax-dynamic-import-7.8.3.tgz?cache=0&sync_timestamp=1578950368021&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-dynamic-import%2Fdownload%2F%40babel%2Fplugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha1-Yr+Ysto80h1iYVT8lu5bPLaOrLM= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-json-strings@^7.8.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-json-strings/download/@babel/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha1-AcohtmjNghjJ5kDLbdiMVBKyyWo= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.2.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-jsx/download/@babel/plugin-syntax-jsx-7.8.3.tgz?cache=0&sync_timestamp=1578978923741&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-jsx%2Fdownload%2F%40babel%2Fplugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" - integrity sha1-UhsGyDxASA8eWLT9M7kuzrHW6pQ= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-nullish-coalescing-operator/download/@babel/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha1-Fn7XA2iIYIH3S1w2xlqIwDtm0ak= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-numeric-separator/download/@babel/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" - integrity sha1-Dj+2Pgm+obEelkZyccgwgAfnxB8= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-object-rest-spread@^7.8.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-object-rest-spread/download/@babel/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha1-YOIl7cvZimQDMqLnLdPmbxr1WHE= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-optional-catch-binding/download/@babel/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha1-YRGiZbz7Ag6579D9/X0mQCue1sE= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.0": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-optional-chaining/download/@babel/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha1-T2nCq5UWfgGAzVM2YT+MV4j31Io= - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-syntax-top-level-await/download/@babel/plugin-syntax-top-level-await-7.8.3.tgz?cache=0&sync_timestamp=1578951710861&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-top-level-await%2Fdownload%2F%40babel%2Fplugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" - integrity sha1-Os3s5pXmsTqvV/wpHRqACVDHE5E= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-arrow-functions/download/@babel/plugin-transform-arrow-functions-7.8.3.tgz?cache=0&sync_timestamp=1578951715748&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-arrow-functions%2Fdownload%2F%40babel%2Fplugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" - integrity sha1-gndsLtDNnhpJlW2uuJYCTJRzuLY= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-async-to-generator/download/@babel/plugin-transform-async-to-generator-7.8.3.tgz?cache=0&sync_timestamp=1578951980365&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-async-to-generator%2Fdownload%2F%40babel%2Fplugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" - integrity sha1-Qwj60NlAnXHq+5sabuNfnWS2QIY= - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" - -"@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-block-scoped-functions/download/@babel/plugin-transform-block-scoped-functions-7.8.3.tgz?cache=0&sync_timestamp=1578978928505&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-block-scoped-functions%2Fdownload%2F%40babel%2Fplugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" - integrity sha1-Q37sW3mbWFIHIISzrl72boNJ6KM= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-block-scoping@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-block-scoping/download/@babel/plugin-transform-block-scoping-7.8.3.tgz?cache=0&sync_timestamp=1578978927897&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-block-scoping%2Fdownload%2F%40babel%2Fplugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha1-l9Ndq2aFekN8FmNYuR0JBQyGjzo= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - lodash "^4.17.13" - -"@babel/plugin-transform-classes@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-classes/download/@babel/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" - integrity sha1-gAWX3biu/CwpPtJ0WcH8yTWibCw= - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-computed-properties/download/@babel/plugin-transform-computed-properties-7.8.3.tgz?cache=0&sync_timestamp=1578978926881&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-computed-properties%2Fdownload%2F%40babel%2Fplugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" - integrity sha1-ltDSi3985OtbEguy4OlDNDyG+Bs= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-destructuring@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-destructuring/download/@babel/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" - integrity sha1-csl89fOGBK6jq/O5NbDhex23alA= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-dotall-regex/download/@babel/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" - integrity sha1-w8bsXuYSXGmTxcvKINyGIanqem4= - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-duplicate-keys/download/@babel/plugin-transform-duplicate-keys-7.8.3.tgz?cache=0&sync_timestamp=1578951710859&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-duplicate-keys%2Fdownload%2F%40babel%2Fplugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" - integrity sha1-jRLfMJqlN/JyiZxWXqF2jihuIfE= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-exponentiation-operator/download/@babel/plugin-transform-exponentiation-operator-7.8.3.tgz?cache=0&sync_timestamp=1578951976267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-exponentiation-operator%2Fdownload%2F%40babel%2Fplugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" - integrity sha1-WBptf1aXDga/UVYM1k9elHtw17c= - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-for-of/download/@babel/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" - integrity sha1-DyYOJ9PinNG7MSjaXnbHYapsEI4= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-function-name/download/@babel/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" - integrity sha1-J5NzyycyKqrWfCaD53bfxHGW7Ys= - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-literals/download/@babel/plugin-transform-literals-7.8.3.tgz?cache=0&sync_timestamp=1578951710851&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-literals%2Fdownload%2F%40babel%2Fplugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" - integrity sha1-rvI5gj2RmU7Hto5VGTUl1229XcE= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-member-expression-literals/download/@babel/plugin-transform-member-expression-literals-7.8.3.tgz?cache=0&sync_timestamp=1578951710812&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-member-expression-literals%2Fdownload%2F%40babel%2Fplugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" - integrity sha1-lj/tS2IKx8v2Apx1VCQCn6OkBBA= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-amd/download/@babel/plugin-transform-modules-amd-7.9.0.tgz?cache=0&sync_timestamp=1584746129321&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-amd%2Fdownload%2F%40babel%2Fplugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha1-GXVe5yGRLPW7BMB9UCgK80hO/vQ= - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-commonjs/download/@babel/plugin-transform-modules-commonjs-7.9.0.tgz?cache=0&sync_timestamp=1584746127807&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-commonjs%2Fdownload%2F%40babel%2Fplugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha1-4+cvTLybSiYOML4OpZvfWjl0iUA= - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-systemjs/download/@babel/plugin-transform-modules-systemjs-7.9.0.tgz?cache=0&sync_timestamp=1584746128326&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-systemjs%2Fdownload%2F%40babel%2Fplugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha1-6f1Gopb8keAJtk4H3aqG1vDt65A= - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-umd@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-modules-umd/download/@babel/plugin-transform-modules-umd-7.9.0.tgz?cache=0&sync_timestamp=1584746128785&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-umd%2Fdownload%2F%40babel%2Fplugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" - integrity sha1-6Qmsridv7CgPm4IaXzjh8ItIBpc= - dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-named-capturing-groups-regex/download/@babel/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" - integrity sha1-oqcr/6ICrA4tBQav0JOcXsvEjGw= - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - -"@babel/plugin-transform-new-target@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-new-target/download/@babel/plugin-transform-new-target-7.8.3.tgz?cache=0&sync_timestamp=1578951725738&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-new-target%2Fdownload%2F%40babel%2Fplugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" - integrity sha1-YMwq5m2FyVq1QOs0urtkNNTHDEM= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-object-super@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-object-super/download/@babel/plugin-transform-object-super-7.8.3.tgz?cache=0&sync_timestamp=1578960959465&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-object-super%2Fdownload%2F%40babel%2Fplugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" - integrity sha1-67ah56hv+paFi9asAQLWWUQmFyU= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-parameters/download/@babel/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" - integrity sha1-FzsmV0b14Vsq/lJ+7aZbc2I6B5U= - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-property-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-property-literals/download/@babel/plugin-transform-property-literals-7.8.3.tgz?cache=0&sync_timestamp=1578951730894&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-property-literals%2Fdownload%2F%40babel%2Fplugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" - integrity sha1-MxlDANhTnB7SjGKtUIe6OAe5gmM= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-regenerator@^7.8.7": - version "7.8.7" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-regenerator/download/@babel/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" - integrity sha1-Xkag3KK+4a2ChesFJ+arycN2cvg= - dependencies: - regenerator-transform "^0.14.2" - -"@babel/plugin-transform-reserved-words@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-reserved-words/download/@babel/plugin-transform-reserved-words-7.8.3.tgz?cache=0&sync_timestamp=1578951715850&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-reserved-words%2Fdownload%2F%40babel%2Fplugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" - integrity sha1-mgY1rE5mXSmxYoN908xQdF398fU= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-runtime@^7.9.0": - version "7.9.0" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-runtime/download/@babel/plugin-transform-runtime-7.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-runtime%2Fdownload%2F%40babel%2Fplugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha1-RUaMCudMwTIE4dOx9M5u6DJYrws= - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - resolve "^1.8.1" - semver "^5.5.1" - -"@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-shorthand-properties/download/@babel/plugin-transform-shorthand-properties-7.8.3.tgz?cache=0&sync_timestamp=1578951715760&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-shorthand-properties%2Fdownload%2F%40babel%2Fplugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" - integrity sha1-KFRSFuAjqDLU06EYXtSSvP6sCMg= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-spread/download/@babel/plugin-transform-spread-7.8.3.tgz?cache=0&sync_timestamp=1578951725794&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-spread%2Fdownload%2F%40babel%2Fplugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha1-nI/+gXD9+4ixFOy5ILgvtulf5eg= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-sticky-regex/download/@babel/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" - integrity sha1-vnoSkPgdrnZ0dUUhmeH3bWF1sQA= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-regex" "^7.8.3" - -"@babel/plugin-transform-template-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-template-literals/download/@babel/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" - integrity sha1-e/pHMrRV6mpDEwrcC6dn7A5AKoA= - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.8.4" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-typeof-symbol/download/@babel/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" - integrity sha1-7eQGIxXOCq+KZXqSCFjxovNfxBI= - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npm.taobao.org/@babel/plugin-transform-unicode-regex/download/@babel/plugin-transform-unicode-regex-7.8.3.tgz?cache=0&sync_timestamp=1578951985656&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-unicode-regex%2Fdownload%2F%40babel%2Fplugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" - integrity sha1-DO8247pz5cVyc+/7GC9GuRoeyq0= - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/preset-env@^7.9.0": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/preset-env/download/@babel/preset-env-7.9.5.tgz?cache=0&sync_timestamp=1586287812297&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fpreset-env%2Fdownload%2F%40babel%2Fpreset-env-7.9.5.tgz#8ddc76039bc45b774b19e2fc548f6807d8a8919f" - integrity sha1-jdx2A5vEW3dLGeL8VI9oB9iokZ8= - dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.5" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.8.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.9.5" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.9.5" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.9.0" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" - "@babel/plugin-transform-modules-umd" "^7.9.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.9.5" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.5" - browserslist "^4.9.1" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/preset-modules@^0.1.3": - version "0.1.3" - resolved "https://registry.npm.taobao.org/@babel/preset-modules/download/@babel/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" - integrity sha1-EyQrU7XvjIg8PPfd3VWzbOgPvHI= - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.9.2" - resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.9.2.tgz?cache=0&sync_timestamp=1584800151099&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha1-2Q3wWDo6JS8JqqYZZlNnuuUY2wY= - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@^7.8.3", "@babel/template@^7.8.6": - version "7.8.6" - resolved "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha1-hrIq8V+CjfsIZHT5ZNzD45xDzis= - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/traverse/download/@babel/traverse-7.9.5.tgz?cache=0&sync_timestamp=1586287811320&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftraverse%2Fdownload%2F%40babel%2Ftraverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha1-bnxWtE4qxwEalIwh4oPd2dnbl6I= - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": - version "7.9.5" - resolved "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha1-iSMfgpFailZqcDs7IBM/c9prlEQ= - dependencies: - "@babel/helper-validator-identifier" "^7.9.5" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@csstools/convert-colors@^1.4.0": - version "1.4.0" - resolved "https://registry.npm.taobao.org/@csstools/convert-colors/download/@csstools/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" - integrity sha1-rUldxBsS511YjG24uYNPCPoTHrc= - -"@nuxt/babel-preset-app@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/babel-preset-app/download/@nuxt/babel-preset-app-2.12.2.tgz#0e87c4f4f578868e74f6fa9e2a38cace5c77dac8" - integrity sha1-DofE9PV4ho509vqeKjjKzlx32sg= - dependencies: - "@babel/core" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/plugin-proposal-class-properties" "^7.8.3" - "@babel/plugin-proposal-decorators" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.9.0" - "@babel/preset-env" "^7.9.0" - "@babel/runtime" "^7.9.2" - "@vue/babel-preset-jsx" "^1.1.2" - core-js "^2.6.5" - -"@nuxt/builder@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/builder/download/@nuxt/builder-2.12.2.tgz#65348dc2524fd67702e8e86cde7af7d4da4c6eb7" - integrity sha1-ZTSNwlJP1ncC6Ohs3nr31NpMbrc= - dependencies: - "@nuxt/devalue" "^1.2.4" - "@nuxt/utils" "2.12.2" - "@nuxt/vue-app" "2.12.2" - "@nuxt/webpack" "2.12.2" - chalk "^3.0.0" - chokidar "^3.3.1" - consola "^2.11.3" - fs-extra "^8.1.0" - glob "^7.1.6" - hash-sum "^2.0.0" - ignore "^5.1.4" - lodash "^4.17.15" - pify "^4.0.1" - semver "^7.1.3" - serialize-javascript "^3.0.0" - upath "^1.2.0" - -"@nuxt/cli@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/cli/download/@nuxt/cli-2.12.2.tgz#fb2a5278e9cda89010fbdcd8515257ae09960d17" - integrity sha1-+ypSeOnNqJAQ+9zYUVJXrgmWDRc= - dependencies: - "@nuxt/config" "2.12.2" - "@nuxt/utils" "2.12.2" - boxen "^4.2.0" - chalk "^3.0.0" - consola "^2.11.3" - esm "^3.2.25" - execa "^3.4.0" - exit "^0.1.2" - fs-extra "^8.1.0" - hable "^3.0.0" - minimist "^1.2.5" - opener "1.5.1" - pretty-bytes "^5.3.0" - std-env "^2.2.1" - wrap-ansi "^6.2.0" - -"@nuxt/config@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/config/download/@nuxt/config-2.12.2.tgz#df0ac32db2c7b36b5bcafc985bff1219dfe5391c" - integrity sha1-3wrDLbLHs2tbyvyYW/8SGd/lORw= - dependencies: - "@nuxt/utils" "2.12.2" - consola "^2.11.3" - esm "^3.2.25" - std-env "^2.2.1" - -"@nuxt/core@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/core/download/@nuxt/core-2.12.2.tgz#d690b63c63ab7c2aec1bf5e6f4dea5c2da454f0c" - integrity sha1-1pC2PGOrfCrsG/Xm9N6lwtpFTww= - dependencies: - "@nuxt/config" "2.12.2" - "@nuxt/devalue" "^1.2.4" - "@nuxt/server" "2.12.2" - "@nuxt/utils" "2.12.2" - "@nuxt/vue-renderer" "2.12.2" - consola "^2.11.3" - debug "^4.1.1" - esm "^3.2.25" - fs-extra "^8.1.0" - hable "^3.0.0" - hash-sum "^2.0.0" - std-env "^2.2.1" - -"@nuxt/devalue@^1.2.4": - version "1.2.4" - resolved "https://registry.npm.taobao.org/@nuxt/devalue/download/@nuxt/devalue-1.2.4.tgz#69eca032b7481fd3c019a78ade65d642da3f2f35" - integrity sha1-aeygMrdIH9PAGaeK3mXWQto/LzU= - dependencies: - consola "^2.9.0" - -"@nuxt/friendly-errors-webpack-plugin@^2.5.0": - version "2.5.0" - resolved "https://registry.npm.taobao.org/@nuxt/friendly-errors-webpack-plugin/download/@nuxt/friendly-errors-webpack-plugin-2.5.0.tgz#5374665bc72d34b7dbadcc361a4777e3f0f5d46b" - integrity sha1-U3RmW8ctNLfbrcw2Gkd34/D11Gs= - dependencies: - chalk "^2.3.2" - consola "^2.6.0" - error-stack-parser "^2.0.0" - string-width "^2.0.0" - -"@nuxt/generator@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/generator/download/@nuxt/generator-2.12.2.tgz#d5b85bbe865776687dfdadfcebeb7dc862931795" - integrity sha1-1bhbvoZXdmh9/a386+t9yGKTF5U= - dependencies: - "@nuxt/utils" "2.12.2" - chalk "^3.0.0" - consola "^2.11.3" - fs-extra "^8.1.0" - html-minifier "^4.0.0" - -"@nuxt/loading-screen@^1.2.0": - version "1.2.0" - resolved "https://registry.npm.taobao.org/@nuxt/loading-screen/download/@nuxt/loading-screen-1.2.0.tgz#9cfab1e5e421bbaedadb26f7f27a68bcde313c24" - integrity sha1-nPqx5eQhu67a2yb38npovN4xPCQ= - dependencies: - connect "^3.7.0" - fs-extra "^8.1.0" - node-res "^5.0.1" - serve-static "^1.14.1" - -"@nuxt/opencollective@^0.3.0": - version "0.3.0" - resolved "https://registry.npm.taobao.org/@nuxt/opencollective/download/@nuxt/opencollective-0.3.0.tgz#11d8944dcf2d526e31660bb69570be03f8fb72b7" - integrity sha1-EdiUTc8tUm4xZgu2lXC+A/j7crc= - dependencies: - chalk "^2.4.2" - consola "^2.10.1" - node-fetch "^2.6.0" - -"@nuxt/server@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/server/download/@nuxt/server-2.12.2.tgz#03490957e5dd1ab2eeca440afb3286883f8c47db" - integrity sha1-A0kJV+XdGrLuykQK+zKGiD+MR9s= - dependencies: - "@nuxt/config" "2.12.2" - "@nuxt/utils" "2.12.2" - "@nuxt/vue-renderer" "2.12.2" - "@nuxtjs/youch" "^4.2.3" - chalk "^3.0.0" - compression "^1.7.4" - connect "^3.7.0" - consola "^2.11.3" - etag "^1.8.1" - fresh "^0.5.2" - fs-extra "^8.1.0" - ip "^1.1.5" - launch-editor-middleware "^2.2.1" - on-headers "^1.0.2" - pify "^4.0.1" - serve-placeholder "^1.2.2" - serve-static "^1.14.1" - server-destroy "^1.0.1" - -"@nuxt/utils@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/utils/download/@nuxt/utils-2.12.2.tgz#5634e5b150c8046ffddf9a8b77b492ebcf93d321" - integrity sha1-VjTlsVDIBG/935qLd7SS68+T0yE= - dependencies: - consola "^2.11.3" - fs-extra "^8.1.0" - hash-sum "^2.0.0" - proper-lockfile "^4.1.1" - semver "^7.1.3" - serialize-javascript "^3.0.0" - signal-exit "^3.0.2" - ua-parser-js "^0.7.21" - -"@nuxt/vue-app@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/vue-app/download/@nuxt/vue-app-2.12.2.tgz#cc4b68356996eb71d398a30f3b9c9d15f7d531bc" - integrity sha1-zEtoNWmW63HTmKMPO5ydFffVMbw= - dependencies: - node-fetch "^2.6.0" - unfetch "^4.1.0" - vue "^2.6.11" - vue-client-only "^2.0.0" - vue-meta "^2.3.3" - vue-no-ssr "^1.1.1" - vue-router "^3.1.6" - vue-template-compiler "^2.6.11" - vuex "^3.1.3" - -"@nuxt/vue-renderer@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/vue-renderer/download/@nuxt/vue-renderer-2.12.2.tgz#cc17d8183a3fcda665eaa176691f25f9589b7d82" - integrity sha1-zBfYGDo/zaZl6qF2aR8l+VibfYI= - dependencies: - "@nuxt/devalue" "^1.2.4" - "@nuxt/utils" "2.12.2" - consola "^2.11.3" - fs-extra "^8.1.0" - lru-cache "^5.1.1" - vue "^2.6.11" - vue-meta "^2.3.3" - vue-server-renderer "^2.6.11" - -"@nuxt/webpack@2.12.2": - version "2.12.2" - resolved "https://registry.npm.taobao.org/@nuxt/webpack/download/@nuxt/webpack-2.12.2.tgz#21cac1e6030d384191afc9260b5cd9edebdaa5d6" - integrity sha1-IcrB5gMNOEGRr8kmC1zZ7evapdY= - dependencies: - "@babel/core" "^7.9.0" - "@nuxt/babel-preset-app" "2.12.2" - "@nuxt/friendly-errors-webpack-plugin" "^2.5.0" - "@nuxt/utils" "2.12.2" - babel-loader "^8.1.0" - cache-loader "^4.1.0" - caniuse-lite "^1.0.30001036" - chalk "^3.0.0" - consola "^2.11.3" - css-loader "^3.4.2" - cssnano "^4.1.10" - eventsource-polyfill "^0.9.6" - extract-css-chunks-webpack-plugin "^4.7.4" - file-loader "^4.3.0" - glob "^7.1.6" - hard-source-webpack-plugin "^0.13.1" - hash-sum "^2.0.0" - html-webpack-plugin "^3.2.0" - memory-fs "^0.4.1" - optimize-css-assets-webpack-plugin "^5.0.3" - pify "^4.0.1" - postcss "^7.0.27" - postcss-import "^12.0.1" - postcss-import-resolver "^2.0.0" - postcss-loader "^3.0.0" - postcss-preset-env "^6.7.0" - postcss-url "^8.0.0" - semver "^7.1.3" - std-env "^2.2.1" - style-resources-loader "^1.3.3" - terser-webpack-plugin "^2.3.5" - thread-loader "^2.1.3" - time-fix-plugin "^2.0.6" - url-loader "^2.3.0" - vue-loader "^15.9.1" - webpack "^4.42.1" - webpack-bundle-analyzer "^3.6.1" - webpack-dev-middleware "^3.7.2" - webpack-hot-middleware "^2.25.0" - webpack-node-externals "^1.7.2" - webpackbar "^4.0.0" - -"@nuxtjs/axios@^5.3.6": - version "5.9.7" - resolved "https://registry.npm.taobao.org/@nuxtjs/axios/download/@nuxtjs/axios-5.9.7.tgz#ec78b72dbcb70fceee7724b7f24e0cb4d924440c" - integrity sha1-7Hi3Lby3D87udyS38k4MtNkkRAw= - dependencies: - "@nuxtjs/proxy" "^1.3.3" - axios "^0.19.2" - axios-retry "^3.1.2" - consola "^2.11.3" - defu "^1.0.0" - -"@nuxtjs/google-gtag@^1.0.4": - version "1.0.4" - resolved "https://registry.npm.taobao.org/@nuxtjs/google-gtag/download/@nuxtjs/google-gtag-1.0.4.tgz#57562d8ec4c7694573e77edf72097da4a34b0d68" - integrity sha1-V1YtjsTHaUVz537fcgl9pKNLDWg= - -"@nuxtjs/proxy@^1.3.3": - version "1.3.3" - resolved "https://registry.npm.taobao.org/@nuxtjs/proxy/download/@nuxtjs/proxy-1.3.3.tgz#3de3d9f073e8e57167168100940be2a824a220e0" - integrity sha1-PePZ8HPo5XFnFoEAlAviqCSiIOA= - dependencies: - consola "^2.5.6" - http-proxy-middleware "^0.19.1" - -"@nuxtjs/pwa@https://github.com/fantix/pwa-module": - version "3.0.0-beta.19" - resolved "https://github.com/fantix/pwa-module#4242928387befe120872899231b78a8d4430dde5" - dependencies: - defu "^0.0.3" - execa "^1.0.0" - fs-extra "^8.1.0" - hasha "^5.0.0" - sharp "^0.24.0" - workbox-cdn "^4.3.1" - -"@nuxtjs/youch@^4.2.3": - version "4.2.3" - resolved "https://registry.npm.taobao.org/@nuxtjs/youch/download/@nuxtjs/youch-4.2.3.tgz#36f8b22df5a0efaa81373109851e1d857aca6bed" - integrity sha1-NviyLfWg76qBNzEJhR4dhXrKa+0= - dependencies: - cookie "^0.3.1" - mustache "^2.3.0" - stack-trace "0.0.10" - -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.npm.taobao.org/@types/color-name/download/@types/color-name-1.1.1.tgz?cache=0&sync_timestamp=1580842268764&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fcolor-name%2Fdownload%2F%40types%2Fcolor-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha1-HBJhu+qhCoBVu8XYq4S3sq/IRqA= - -"@types/q@^1.5.1": - version "1.5.2" - resolved "https://registry.npm.taobao.org/@types/q/download/@types/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" - integrity sha1-aQoUdbhPKohP0HzXl8APXzE1bqg= - -"@vue/babel-helper-vue-jsx-merge-props@^1.0.0": - version "1.0.0" - resolved "https://registry.npm.taobao.org/@vue/babel-helper-vue-jsx-merge-props/download/@vue/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040" - integrity sha1-BI/leZWNpAj7eosqPsBQtQpmEEA= - -"@vue/babel-plugin-transform-vue-jsx@^1.1.2": - version "1.1.2" - resolved "https://registry.npm.taobao.org/@vue/babel-plugin-transform-vue-jsx/download/@vue/babel-plugin-transform-vue-jsx-1.1.2.tgz#c0a3e6efc022e75e4247b448a8fc6b86f03e91c0" - integrity sha1-wKPm78Ai515CR7RIqPxrhvA+kcA= - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" - html-tags "^2.0.0" - lodash.kebabcase "^4.1.1" - svg-tags "^1.0.0" - -"@vue/babel-preset-jsx@^1.1.2": - version "1.1.2" - resolved "https://registry.npm.taobao.org/@vue/babel-preset-jsx/download/@vue/babel-preset-jsx-1.1.2.tgz#2e169eb4c204ea37ca66c2ea85a880bfc99d4f20" - integrity sha1-LhaetMIE6jfKZsLqhaiAv8mdTyA= - dependencies: - "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" - "@vue/babel-sugar-functional-vue" "^1.1.2" - "@vue/babel-sugar-inject-h" "^1.1.2" - "@vue/babel-sugar-v-model" "^1.1.2" - "@vue/babel-sugar-v-on" "^1.1.2" - -"@vue/babel-sugar-functional-vue@^1.1.2": - version "1.1.2" - resolved "https://registry.npm.taobao.org/@vue/babel-sugar-functional-vue/download/@vue/babel-sugar-functional-vue-1.1.2.tgz#f7e24fba09e6f1ee70104560a8808057555f1a9a" - integrity sha1-9+JPugnm8e5wEEVgqICAV1VfGpo= - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - -"@vue/babel-sugar-inject-h@^1.1.2": - version "1.1.2" - resolved "https://registry.npm.taobao.org/@vue/babel-sugar-inject-h/download/@vue/babel-sugar-inject-h-1.1.2.tgz#8a5276b6d8e2ed16ffc8078aad94236274e6edf0" - integrity sha1-ilJ2ttji7Rb/yAeKrZQjYnTm7fA= - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - -"@vue/babel-sugar-v-model@^1.1.2": - version "1.1.2" - resolved "https://registry.npm.taobao.org/@vue/babel-sugar-v-model/download/@vue/babel-sugar-v-model-1.1.2.tgz#1ff6fd1b800223fc9cb1e84dceb5e52d737a8192" - integrity sha1-H/b9G4ACI/ycsehNzrXlLXN6gZI= - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" - camelcase "^5.0.0" - html-tags "^2.0.0" - svg-tags "^1.0.0" - -"@vue/babel-sugar-v-on@^1.1.2": - version "1.1.2" - resolved "https://registry.npm.taobao.org/@vue/babel-sugar-v-on/download/@vue/babel-sugar-v-on-1.1.2.tgz#b2ef99b8f2fab09fbead25aad70ef42e1cf5b13b" - integrity sha1-su+ZuPL6sJ++rSWq1w70Lhz1sTs= - dependencies: - "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" - camelcase "^5.0.0" - -"@vue/component-compiler-utils@^3.1.0": - version "3.1.2" - resolved "https://registry.npm.taobao.org/@vue/component-compiler-utils/download/@vue/component-compiler-utils-3.1.2.tgz#8213a5ff3202f9f2137fe55370f9e8b9656081c3" - integrity sha1-ghOl/zIC+fITf+VTcPnouWVggcM= - dependencies: - consolidate "^0.15.1" - hash-sum "^1.0.2" - lru-cache "^4.1.2" - merge-source-map "^1.1.0" - postcss "^7.0.14" - postcss-selector-parser "^6.0.2" - source-map "~0.6.1" - vue-template-es2015-compiler "^1.9.0" - optionalDependencies: - prettier "^1.18.2" - -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/ast/download/@webassemblyjs/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha1-vYUGBLQEJFmlpBzX0zjL7Wle2WQ= - dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/floating-point-hex-parser/download/@webassemblyjs/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha1-PD07Jxvd/ITesA9xNEQ4MR1S/7Q= - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-api-error/download/@webassemblyjs/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha1-ID9nbjM7lsnaLuqzzO8zxFkotqI= - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-buffer/download/@webassemblyjs/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha1-oUQtJpxf6yP8vJ73WdrDVH8p3gA= - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-code-frame/download/@webassemblyjs/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha1-ZH+Iks0gQ6gqwMjF51w28dkVnyc= - dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-fsm/download/@webassemblyjs/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha1-wFJWtxJEIUZx9LCOwQitY7cO3bg= - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-module-context/download/@webassemblyjs/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha1-JdiIS3aDmHGgimxvgGw5ee9xLwc= - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-wasm-bytecode/download/@webassemblyjs/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha1-T+2L6sm4wU+MWLcNEk1UndH+V5A= - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/helper-wasm-section/download/@webassemblyjs/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha1-WkE41aYpK6GLBMWuSXF+QWeWU0Y= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/ieee754/download/@webassemblyjs/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha1-Fceg+6roP7JhQ7us9tbfFwKtOeQ= - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/leb128/download/@webassemblyjs/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha1-8Zygt2ptxVYjoJz/p2noOPoeHJU= - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/utf8/download/@webassemblyjs/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha1-BNM7Y2945qaBMifoJAL3Y3tiKas= - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/wasm-edit/download/@webassemblyjs/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha1-P+bXnT8PkiGDqoYALELdJWz+6c8= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/wasm-gen/download/@webassemblyjs/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha1-ULxw7Gje2OJ2OwGhQYv0NJGnpJw= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/wasm-opt/download/@webassemblyjs/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha1-IhEYHlsxMmRDzIES658LkChyGmE= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/wasm-parser/download/@webassemblyjs/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha1-nUjkSCbfSmWYKUqmyHRp1kL/9l4= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/wast-parser/download/@webassemblyjs/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha1-MDERXXmsW9JhVWzsw/qQo+9FGRQ= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.npm.taobao.org/@webassemblyjs/wast-printer/download/@webassemblyjs/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha1-STXVTIX+9jewDOn1I3dFHQDUeJk= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.npm.taobao.org/@xtuc/ieee754/download/@xtuc/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha1-7vAUoxRa5Hehy8AM0eVSM23Ot5A= - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.npm.taobao.org/@xtuc/long/download/@xtuc/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0= - -abbrev@1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/abbrev/download/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg= - -accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha1-UxvHJlF6OytB+FACHGzBXqq1B80= - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-walk@^7.1.1: - version "7.1.1" - resolved "https://registry.npm.taobao.org/acorn-walk/download/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" - integrity sha1-NF8N/61cc15zc9L+yaECPmpEuD4= - -acorn@^6.2.1: - version "6.4.1" - resolved "https://registry.npm.taobao.org/acorn/download/acorn-6.4.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha1-Ux5Yuj9RudrLmmZGyk3r9bFMpHQ= - -acorn@^7.1.1: - version "7.1.1" - resolved "https://registry.npm.taobao.org/acorn/download/acorn-7.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" - integrity sha1-41Zo3gtALzWd5RXFSCoaufiaab8= - -aggregate-error@^3.0.0: - version "3.0.1" - resolved "https://registry.npm.taobao.org/aggregate-error/download/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" - integrity sha1-2y/nJG5Tb0DZtUQqOeEX191qJOA= - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/ajv-errors/download/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha1-81mGrOuRr63sQQL72FAUlQzvpk0= - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.4.1" - resolved "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha1-75FuJxxkrBIXH9g4TqrmsjRYVNo= - -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.npm.taobao.org/ajv/download/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha1-BtYLlth7hFSlrauobnhU2mKdtLc= - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/alphanum-sort/download/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.npm.taobao.org/amdefine/download/amdefine-1.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Famdefine%2Fdownload%2Famdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/ansi-align/download/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha1-tTazcc9ofKrvI2wY0+If43l0Z8s= - dependencies: - string-width "^3.0.0" - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.npm.taobao.org/ansi-colors/download/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha1-46PaS/uubIapwoViXeEkojQCb78= - -ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-4.3.1.tgz?cache=0&sync_timestamp=1583073192260&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-escapes%2Fdownload%2Fansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha1-pcR8xDGB8fOP/XB2g3cA05VSKmE= - dependencies: - type-fest "^0.11.0" - -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.npm.taobao.org/ansi-html/download/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc= - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0= - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha1-kK51xCTQCNJiTFvynq0xd+v881k= - dependencies: - "@types/color-name" "^1.1.1" - color-convert "^2.0.1" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/anymatch/download/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha1-vLJLTzeTTZqnrBe0ra+J58du8us= - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha1-xV7PAhheJGklk5kxDBc84xIzsUI= - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.0.3, aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.npm.taobao.org/aproba/download/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha1-aALmJk79GMeQobDVF/DyYnvyyUo= - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.npm.taobao.org/are-we-there-yet/download/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha1-SzXClE8GKov82mZBB2A1D+nd/CE= - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npm.taobao.org/argparse/download/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE= - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/arr-diff/download/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/arr-flatten/download/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha1-NgSLv/TntH4TZkQxbJlmnqWukfE= - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/arr-union/download/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/array-find-index/download/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.npm.taobao.org/array-unique/download/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.npm.taobao.org/asn1.js/download/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha1-ucK/WAXx5kqt7tbfOiv6+1pz9aA= - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.npm.taobao.org/asn1/download/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha1-jSR136tVO7M+d7VOWeiAu4ziMTY= - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.npm.taobao.org/assert/download/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha1-VcEJqvbgrv2z3EtxJAxwv1dLGOs= - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assert@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/assert/download/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" - integrity sha1-lfwcYW1IcTUQaA8ury0Q3SLgLTI= - dependencies: - es6-object-assign "^1.1.0" - is-nan "^1.2.1" - object-is "^1.0.1" - util "^0.12.0" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/assign-symbols/download/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.npm.taobao.org/async-each/download/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha1-tyfb+H12UWAvBvTUrDh/R9kbDL8= - -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.npm.taobao.org/async-foreach/download/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/async-limiter/download/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha1-3TeelPDbgxCwgpH51kwyCXZmF/0= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.npm.taobao.org/atob/download/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k= - -autoprefixer@^9.6.1: - version "9.7.6" - resolved "https://registry.npm.taobao.org/autoprefixer/download/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4" - integrity sha1-Y6xbvAznk05plyB9W7ANaPqCk6Q= - dependencies: - browserslist "^4.11.1" - caniuse-lite "^1.0.30001039" - chalk "^2.4.2" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.27" - postcss-value-parser "^4.0.3" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.npm.taobao.org/aws4/download/aws4-1.9.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faws4%2Fdownload%2Faws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha1-fjPY99RJs/ZzzXLeuavcVS2+Uo4= - -axios-retry@^3.1.2: - version "3.1.2" - resolved "https://registry.npm.taobao.org/axios-retry/download/axios-retry-3.1.2.tgz#4f4dcbefb0b434e22b72bd5e28a027d77b8a3458" - integrity sha1-T03L77C0NOIrcr1eKKAn13uKNFg= - dependencies: - is-retry-allowed "^1.1.0" - -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.npm.taobao.org/axios/download/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha1-PqNsXYgY0NX4qKl6bTa4bNwAyyc= - dependencies: - follow-redirects "1.5.10" - -babel-loader@^8.1.0: - version "8.1.0" - resolved "https://registry.npm.taobao.org/babel-loader/download/babel-loader-8.1.0.tgz?cache=0&sync_timestamp=1584715959282&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-loader%2Fdownload%2Fbabel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" - integrity sha1-xhHVESvVIJq+i5+oTD5NolJ18cM= - dependencies: - find-cache-dir "^2.1.0" - loader-utils "^1.4.0" - mkdirp "^0.5.3" - pify "^4.0.1" - schema-utils "^2.6.5" - -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.npm.taobao.org/babel-plugin-dynamic-import-node/download/babel-plugin-dynamic-import-node-2.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-plugin-dynamic-import-node%2Fdownload%2Fbabel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha1-8A9Qe9qjw+P/bn5emNkKesq5b38= - dependencies: - object.assign "^4.1.0" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.npm.taobao.org/base/download/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha1-e95c7RRbbVUakNuH+DxVi060io8= - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/bcrypt-pbkdf/download/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -bfj@^6.1.1: - version "6.1.2" - resolved "https://registry.npm.taobao.org/bfj/download/bfj-6.1.2.tgz?cache=0&sync_timestamp=1577112259978&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbfj%2Fdownload%2Fbfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f" - integrity sha1-MlyGGoIryzWKQceKM7jm4ght3n8= - dependencies: - bluebird "^3.5.5" - check-types "^8.0.3" - hoopy "^0.1.4" - tryer "^1.0.1" - -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.npm.taobao.org/big.js/download/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4= - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.npm.taobao.org/big.js/download/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg= - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U= - -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha1-I8DfFPaogHf1+YbA0WfsA8PVU3w= - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.npm.taobao.org/bindings/download/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha1-EDU8npRTNLwFEabZCzj7x8nFBN8= - dependencies: - file-uri-to-path "1.0.0" - -bl@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -block-stream@*: - version "0.0.9" - resolved "https://registry.npm.taobao.org/block-stream/download/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - -bluebird@^3.1.1, bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.npm.taobao.org/bluebird/download/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha1-nyKcFb4nJFT/qXOs4NvueaGww28= - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io= - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/boolbase/download/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= - -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.npm.taobao.org/boxen/download/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha1-5BG2I1fW1tNlh8isPV2XTaoHDmQ= - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0= - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.npm.taobao.org/braces/download/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha1-WXn9PxTNUxVl5fot8av/8d+u5yk= - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha1-NFThpGLujVmeI23zNs2epPiv4Qc= - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.npm.taobao.org/browserify-aes/download/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha1-Mmc0ZC9APavDADIJhTu3CtQo70g= - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/browserify-cipher/download/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha1-jWR0wbhwv9q807z8wZNKEOlPFfA= - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/browserify-des/download/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha1-OvTx9Zg5QDVy8cZiBDdfen9wPpw= - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.npm.taobao.org/browserify-rsa/download/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.npm.taobao.org/browserify-sign/download/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.npm.taobao.org/browserify-zlib/download/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha1-KGlFnZqjviRf6P4sofRuLn9U1z8= - dependencies: - pako "~1.0.5" - -browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.4, browserslist@^4.8.5, browserslist@^4.9.1: - version "4.11.1" - resolved "https://registry.npm.taobao.org/browserslist/download/browserslist-4.11.1.tgz?cache=0&sync_timestamp=1585628115808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha1-kvhV7ojW4FDn5zEdmHmSAU8aHxs= - dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" - node-releases "^1.1.53" - pkg-up "^2.0.0" - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8= - -buffer-json@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/buffer-json/download/buffer-json-2.0.0.tgz#f73e13b1e42f196fe2fd67d001c7d7107edd7c23" - integrity sha1-9z4TseQvGW/i/WfQAcfXEH7dfCM= - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.npm.taobao.org/buffer-xor/download/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.npm.taobao.org/buffer/download/buffer-4.9.2.tgz?cache=0&sync_timestamp=1583524806468&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbuffer%2Fdownload%2Fbuffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha1-Iw6tNEACmIZEhBqwJEr4xEu+Pvg= - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -buffer@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/builtin-status-codes/download/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbytes%2Fdownload%2Fbytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbytes%2Fdownload%2Fbytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY= - -cacache@^12.0.2: - version "12.0.4" - resolved "https://registry.npm.taobao.org/cacache/download/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha1-ZovL0QWutfHZL+JVcOyVJcj6pAw= - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^13.0.1: - version "13.0.1" - resolved "https://registry.npm.taobao.org/cacache/download/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" - integrity sha1-qAAMIWlwiQgvhSh6GuxuOCAkpxw= - dependencies: - chownr "^1.1.2" - figgy-pudding "^3.5.1" - fs-minipass "^2.0.0" - glob "^7.1.4" - graceful-fs "^4.2.2" - infer-owner "^1.0.4" - lru-cache "^5.1.1" - minipass "^3.0.0" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - p-map "^3.0.0" - promise-inflight "^1.0.1" - rimraf "^2.7.1" - ssri "^7.0.0" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/cache-base/download/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha1-Cn9GQWgxyLZi7jb+TnxZ129marI= - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cache-loader@^4.1.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/cache-loader/download/cache-loader-4.1.0.tgz#9948cae353aec0a1fcb1eafda2300816ec85387e" - integrity sha1-mUjK41OuwKH8ser9ojAIFuyFOH4= - dependencies: - buffer-json "^2.0.0" - find-cache-dir "^3.0.0" - loader-utils "^1.2.3" - mkdirp "^0.5.1" - neo-async "^2.6.1" - schema-utils "^2.0.0" - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/caller-callsite/download/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/caller-path/download/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/callsites/download/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - -camel-case@3.0.x, camel-case@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" - -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/camelcase-keys/download/camelcase-keys-2.1.0.tgz?cache=0&sync_timestamp=1585886716567&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase-keys%2Fdownload%2Fcamelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-2.1.1.tgz?cache=0&sync_timestamp=1586230174560&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase%2Fdownload%2Fcamelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-3.0.0.tgz?cache=0&sync_timestamp=1586230174560&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase%2Fdownload%2Fcamelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-5.3.1.tgz?cache=0&sync_timestamp=1586230174560&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase%2Fdownload%2Fcamelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA= - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/caniuse-api/download/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA= - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001036, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039: - version "1.0.30001040" - resolved "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001040.tgz#103fc8e6eb1d7397e95134cd0e996743353d58ea" - integrity sha1-ED/I5usdc5fpUTTNDplnQzU9WOo= - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz?cache=0&sync_timestamp=1585815747306&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.npm.taobao.org/chalk/download/chalk-2.4.2.tgz?cache=0&sync_timestamp=1585815747306&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ= - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/chalk/download/chalk-3.0.0.tgz?cache=0&sync_timestamp=1585815747306&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ= - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -check-types@^8.0.3: - version "8.0.3" - resolved "https://registry.npm.taobao.org/check-types/download/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" - integrity sha1-M1bMoZyIlUTy16le1JzlCKDs9VI= - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.npm.taobao.org/chokidar/download/chokidar-2.1.8.tgz?cache=0&sync_timestamp=1584609518131&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha1-gEs6e2qZNYw8XGHnHYco8EHP+Rc= - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chokidar@^3.3.1: - version "3.3.1" - resolved "https://registry.npm.taobao.org/chokidar/download/chokidar-3.3.1.tgz?cache=0&sync_timestamp=1584609518131&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha1-yE5bPRjZpNd1WP70ZrG/FrvrNFA= - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.3.0" - optionalDependencies: - fsevents "~2.1.2" - -chownr@^1.1.1, chownr@^1.1.2, chownr@^1.1.3: - version "1.1.4" - resolved "https://registry.npm.taobao.org/chownr/download/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha1-b8nXtC0ypYNZYzdmbn0ICE2izGs= - -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/chrome-trace-event/download/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha1-I0CQ7pfH1K0aLEvq4nUF3v/GCKQ= - dependencies: - tslib "^1.9.0" - -ci-info@^1.6.0: - version "1.6.0" - resolved "https://registry.npm.taobao.org/ci-info/download/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha1-LKINu5zrMtRSSmgzAzE/AwSx5Jc= - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.npm.taobao.org/cipher-base/download/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94= - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.npm.taobao.org/class-utils/download/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha1-+TNprouafOAv1B+q0MqDAzGQxGM= - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-css@4.2.x, clean-css@^4.2.1: - version "4.2.3" - resolved "https://registry.npm.taobao.org/clean-css/download/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" - integrity sha1-UHtd59l7SO5T2ErbAWD/YhY4D3g= - dependencies: - source-map "~0.6.0" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/clean-stack/download/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha1-7oRy27Ep5yezHooQpCfe6d/kAIs= - -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/cli-boxes/download/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha1-U47K6PnGylCOPDyVtFP+k8tMFo0= - -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/clone-deep/download/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha1-wZ/Zvbv4WUK0/ZechNz31fB8I4c= - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.npm.taobao.org/coa/download/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha1-Q/bCEVG07yv1cYfbDXPeIp4+fsM= - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcode-point-at%2Fdownload%2Fcode-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.1: - version "1.9.3" - resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg= - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM= - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha1-wqCah6y95pVD3m9j+jmVyCbFNqI= - -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.npm.taobao.org/color-string/download/color-string-1.5.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcolor-string%2Fdownload%2Fcolor-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha1-ybvF8BtYtUkvPWhXRZy2WQziBMw= - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0, color@^3.1.2: - version "3.1.2" - resolved "https://registry.npm.taobao.org/color/download/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha1-aBSOf4XUGtdknF+oyBBvCY0inhA= - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha1-w9RaizT9cwYxoRCoolIGgrMdWn8= - dependencies: - delayed-stream "~1.0.0" - -commander@2.17.x: - version "2.17.1" - resolved "https://registry.npm.taobao.org/commander/download/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha1-vXerfebelCBc6sxy8XFtKfIKd78= - -commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: - version "2.20.3" - resolved "https://registry.npm.taobao.org/commander/download/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha1-/UhehMA+tIgcIHIrpIA16FMa6zM= - -commander@~2.19.0: - version "2.19.0" - resolved "https://registry.npm.taobao.org/commander/download/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha1-9hmKqE5bg8RgVLlN3tv+1e6f8So= - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/commondir/download/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A= - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.npm.taobao.org/compressible/download/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha1-r1PMprBw1MPAdQ+9dyhqbXzEb7o= - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.npm.taobao.org/compression/download/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha1-lVI+/xcMpXwpoMpB5v4TH0Hlu48= - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ= - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.npm.taobao.org/connect/download/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha1-XUk0iRDKpeB6AYALAw0MNfIEhPg= - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -consola@^2.10.0, consola@^2.10.1, consola@^2.11.3, consola@^2.5.6, consola@^2.6.0, consola@^2.9.0: - version "2.11.3" - resolved "https://registry.npm.taobao.org/consola/download/consola-2.11.3.tgz#f7315836224c143ac5094b47fd4c816c2cd1560e" - integrity sha1-9zFYNiJMFDrFCUtH/UyBbCzRVg4= - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/console-browserify/download/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha1-ZwY871fOts9Jk6KrOlWECujEkzY= - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/console-control-strings/download/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -consolidate@^0.15.1: - version "0.15.1" - resolved "https://registry.npm.taobao.org/consolidate/download/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" - integrity sha1-IasEMjXHGgfUXZqtmFk7DbpWurc= - dependencies: - bluebird "^3.1.1" - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/constants-browserify/download/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70= - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha1-4TjMdeBAxyexlm/l5fjJruJW/js= - -convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.npm.taobao.org/convert-source-map/download/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha1-F6LLiC1/d9NJBYXizmxSRCSjpEI= - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo= - -cookie@^0.3.1: - version "0.3.1" - resolved "https://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.npm.taobao.org/copy-concurrently/download/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha1-kilzmMrjSTf8r9bsgTnBgFHwteA= - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.npm.taobao.org/copy-descriptor/download/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-js-compat@^3.6.2: - version "3.6.5" - resolved "https://registry.npm.taobao.org/core-js-compat/download/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" - integrity sha1-KlHZpOJd/W5pAlGqgfmePAVIHxw= - dependencies: - browserslist "^4.8.5" - semver "7.0.0" - -core-js@^2.6.5: - version "2.6.11" - resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.11.tgz?cache=0&sync_timestamp=1586450269267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha1-OIMUafmSK97Y7iHJ3EaYXgOZMIw= - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.npm.taobao.org/cosmiconfig/download/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha1-BA9yaAnFked6F8CjYmykW08Wixo= - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.npm.taobao.org/create-ecdh/download/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha1-yREbbzMEXEaX8UR4f5JUzcd8Rf8= - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.npm.taobao.org/create-hash/download/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha1-iJB4rxGmN1a8+1m9IhmWvjqe8ZY= - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.npm.taobao.org/create-hmac/download/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha1-aRcMeLOrlXFHsriwRXLkfq0iQ/8= - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^3.0.0: - version "3.0.1" - resolved "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q= - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.0: - version "7.0.2" - resolved "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-7.0.2.tgz#d0d7dcfa74e89115c7619f4f721a94e1fdb716d6" - integrity sha1-0Nfc+nTokRXHYZ9PchqU4f23FtY= - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.npm.taobao.org/crypto-browserify/download/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha1-OWz58xN/A+S45TLFj2mCVOAPgOw= - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -css-blank-pseudo@^0.1.4: - version "0.1.4" - resolved "https://registry.npm.taobao.org/css-blank-pseudo/download/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" - integrity sha1-3979MlS/ioICeZNnTM81SDv8s8U= - dependencies: - postcss "^7.0.5" - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.npm.taobao.org/css-color-names/download/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/css-declaration-sorter/download/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha1-wZiUD2OnbX42wecQGLABchBUyyI= - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-has-pseudo@^0.10.0: - version "0.10.0" - resolved "https://registry.npm.taobao.org/css-has-pseudo/download/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" - integrity sha1-PGQqs0yiQsWcQaEl35EFhB9pZu4= - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^5.0.0-rc.4" - -css-loader@^3.4.2: - version "3.5.2" - resolved "https://registry.npm.taobao.org/css-loader/download/css-loader-3.5.2.tgz#6483ae56f48a7f901fbe07dde2fc96b01eafab3c" - integrity sha1-ZIOuVvSKf5Afvgfd4vyWsB6vqzw= - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.27" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.3" - schema-utils "^2.6.5" - semver "^6.3.0" - -css-prefers-color-scheme@^3.1.1: - version "3.1.1" - resolved "https://registry.npm.taobao.org/css-prefers-color-scheme/download/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" - integrity sha1-b4MKJxQZnU8NDQu4onkW7WXP8fQ= - dependencies: - postcss "^7.0.5" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.npm.taobao.org/css-select-base-adapter/download/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc= - -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/css-select/download/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/css-select/download/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha1-ajRlM1ZjWTSoG6ymjQJVQyEF2+8= - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha1-mL69YsTB2flg7DQM+fdSLjBwmiI= - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha1-K/8//huz93bPfu/ZHuXLp3oUnus= - dependencies: - mdn-data "2.0.6" - source-map "^0.6.1" - -css-what@2.1: - version "2.1.3" - resolved "https://registry.npm.taobao.org/css-what/download/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha1-ptdgRXM2X+dGhsPzEcVlE9iChfI= - -css-what@^3.2.1: - version "3.2.1" - resolved "https://registry.npm.taobao.org/css-what/download/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" - integrity sha1-9KjxJCEGRiG0VnVeNKA6LCLfXaE= - -cssdb@^4.4.0: - version "4.4.0" - resolved "https://registry.npm.taobao.org/cssdb/download/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" - integrity sha1-O/LypowQ9cagir2SN4Mx7oA83bA= - -cssesc@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/cssesc/download/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha1-OxO9G7HLNuG8taTc0n9UxdyzVwM= - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/cssesc/download/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha1-N3QZGZA7hoVl4cCep0dEXNGJg+4= - -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.npm.taobao.org/cssnano-preset-default/download/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha1-UexmLM/KD4izltzZZ5zbkxvhf3Y= - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" - postcss-unique-selectors "^4.0.1" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/cssnano-util-get-arguments/download/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/cssnano-util-get-match/download/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/cssnano-util-raw-cache/download/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI= - dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.npm.taobao.org/cssnano-util-same-parent/download/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M= - -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.npm.taobao.org/cssnano/download/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha1-CsQfCxPRPUZUh+ERt3jULaYxuLI= - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2: - version "4.0.3" - resolved "https://registry.npm.taobao.org/csso/download/csso-4.0.3.tgz?cache=0&sync_timestamp=1585052807583&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcsso%2Fdownload%2Fcsso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha1-DZmF3IUsfMKyys+74QeQFNGo6QM= - dependencies: - css-tree "1.0.0-alpha.39" - -cuint@^0.2.2: - version "0.2.2" - resolved "https://registry.npm.taobao.org/cuint/download/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" - integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.npm.taobao.org/currently-unhandled/download/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/cyclist/download/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -de-indent@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" - integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= - dependencies: - ms "2.0.0" - -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE= - dependencies: - ms "2.0.0" - -debug@^3.0.0: - version "3.2.6" - resolved "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha1-6D0X3hbYp++3cX7b5fsQE17uYps= - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E= - dependencies: - ms "^2.1.1" - -decamelize@^1.1.1, decamelize@^1.1.2: - version "1.2.0" - resolved "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.npm.taobao.org/decode-uri-component/download/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.npm.taobao.org/decompress-response/download/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha1-QUAjzHowLaJc4uyC0NUjjMr9iYY= - dependencies: - mimic-response "^2.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.npm.taobao.org/deep-extend/download/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw= - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.npm.taobao.org/deepmerge/download/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha1-RNLqNnm49NT/ujPwPYZfwee/SVU= - -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.npm.taobao.org/define-properties/download/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE= - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.npm.taobao.org/define-property/download/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha1-1Flono1lS6d+AqgX+HENcCyxbp0= - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -defu@^0.0.3: - version "0.0.3" - resolved "https://registry.npm.taobao.org/defu/download/defu-0.0.3.tgz#bdc3ea1e1ab2120d4d4a129147f3ba9b7f9fe103" - integrity sha1-vcPqHhqyEg1NShKRR/O6m3+f4QM= - -defu@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/defu/download/defu-1.0.0.tgz#43acb09dfcf81866fa3b0fc047ece18e5c30df71" - integrity sha1-Q6ywnfz4GGb6Ow/AR+zhjlww33E= - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/delegates/download/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/des.js/download/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha1-U4IULhvcU/hdhtU+X0qn3rkeCEM= - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@^1.0.4, destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detect-indent@^5.0.0: - version "5.0.0" - resolved "https://registry.npm.taobao.org/detect-indent/download/detect-indent-5.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdetect-indent%2Fdownload%2Fdetect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= - -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.npm.taobao.org/detect-libc/download/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.npm.taobao.org/diffie-hellman/download/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha1-QOjumPVaIUlgcUaSHGPhrl89KHU= - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dimport@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/dimport/download/dimport-1.0.0.tgz#d5c09564f621e7b24b2e333cccdf9b2303011644" - integrity sha1-1cCVZPYh57JLLjM8zN+bIwMBFkQ= - dependencies: - rewrite-imports "^2.0.3" - -dom-converter@^0.2: - version "0.2.0" - resolved "https://registry.npm.taobao.org/dom-converter/download/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha1-ZyGp2u4uKTaClVtq/kFncWJ7t2g= - dependencies: - utila "~0.4" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.npm.taobao.org/dom-serializer/download/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha1-GvuB9TNxcXXUeGVd68XjMtn5u1E= - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto= - -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.npm.taobao.org/domelementtype/download/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8= - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/domelementtype/download/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha1-H4vf6R9aeAYydOgDtL3O326U+U0= - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.npm.taobao.org/domhandler/download/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha1-iAUJfpM9ZehVRvcm1g9euItE+AM= - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.npm.taobao.org/domutils/download/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1, domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.npm.taobao.org/domutils/download/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo= - dependencies: - dom-serializer "0" - domelementtype "1" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.npm.taobao.org/dot-prop/download/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha1-w07MKVVtxF8fTCJpe29JBODMT8s= - dependencies: - is-obj "^2.0.0" - -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.npm.taobao.org/duplexer/download/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.npm.taobao.org/duplexify/download/duplexify-3.7.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fduplexify%2Fdownload%2Fduplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha1-Kk31MX9sz9kfhtb9JdjYoQO4gwk= - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -ejs@^2.6.1: - version "2.7.4" - resolved "https://registry.npm.taobao.org/ejs/download/ejs-2.7.4.tgz?cache=0&sync_timestamp=1585507343580&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fejs%2Fdownload%2Fejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha1-SGYSh1c9zFPjZsehrlLDoSDuybo= - -electron-to-chromium@^1.3.390: - version "1.3.403" - resolved "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.403.tgz#c8bab4e2e72bf78bc28bad1cc355c061f9cc1918" - integrity sha1-yLq04ucr94vCi60cw1XAYfnMGRg= - -elliptic@^6.0.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-7.0.3.tgz?cache=0&sync_timestamp=1586511256264&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femoji-regex%2Fdownload%2Femoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha1-kzoEBShgyF6DwSJHnEdIqOTHIVY= - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1586511256264&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc= - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/emojis-list/download/emojis-list-2.1.0.tgz?cache=0&sync_timestamp=1563088760941&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femojis-list%2Fdownload%2Femojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/emojis-list/download/emojis-list-3.0.0.tgz?cache=0&sync_timestamp=1563088760941&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femojis-list%2Fdownload%2Femojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha1-VXBmIEatKeLpFucariYKvf9Pang= - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.npm.taobao.org/end-of-stream/download/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha1-WuZKX0UFe682JuwU2gyl5LJDHrA= - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: - version "4.1.1" - resolved "https://registry.npm.taobao.org/enhanced-resolve/download/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" - integrity sha1-KTfiuAZs0P584JkKmPDXGjUYn2Y= - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.npm.taobao.org/entities/download/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha1-vfpzUplmTfr9NFKe1PhSKidf6lY= - -entities@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/entities/download/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha1-aNYITKsbB5dnVA2A5Wo5tCPkq/Q= - -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.npm.taobao.org/errno/download/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg= - dependencies: - prr "~1.0.1" - -error-ex@^1.2.0, error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npm.taobao.org/error-ex/download/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha1-tKxAZIEH/c3PriQvQovqihTU8b8= - dependencies: - is-arrayish "^0.2.1" - -error-stack-parser@^2.0.0: - version "2.0.6" - resolved "https://registry.npm.taobao.org/error-stack-parser/download/error-stack-parser-2.0.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ferror-stack-parser%2Fdownload%2Ferror-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" - integrity sha1-WpmnB716TFinl5AtSNgoA+3mqtg= - dependencies: - stackframe "^1.1.1" - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha1-2MnR1myJgfuSAOIlHXme7pJ3Suk= - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.npm.taobao.org/es-to-primitive/download/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha1-5VzUyc3BiLzvsDs2bHNjI/xciYo= - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es6-object-assign@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/es6-object-assign/download/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" - integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.npm.taobao.org/eslint-scope/download/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha1-ygODMxD2iJoyZHgaqC5j65z+eEg= - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -esm@^3.2.25: - version "3.2.25" - resolved "https://registry.npm.taobao.org/esm/download/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" - integrity sha1-NCwYwp1WFXaIulzjH4Qx+7eVzBA= - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.npm.taobao.org/esprima/download/esprima-4.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fesprima%2Fdownload%2Fesprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha1-E7BM2z5sXRnfkatph6hpVhmwqnE= - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.npm.taobao.org/esrecurse/download/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8= - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npm.taobao.org/estraverse/download/estraverse-4.3.0.tgz?cache=0&sync_timestamp=1584936307490&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Festraverse%2Fdownload%2Festraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha1-OYrT88WiSUi+dyXoPRGn3ijNvR0= - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npm.taobao.org/esutils/download/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q= - -etag@^1.8.1, etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/eventemitter3/download/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - integrity sha1-1lF2FjiH7lnzhtZMgmELaWpKdOs= - -events@^3.0.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/events/download/events-3.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fevents%2Fdownload%2Fevents-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha1-hCea8bNMt1qoi/X/KR9tC9mzGlk= - -eventsource-polyfill@^0.9.6: - version "0.9.6" - resolved "https://registry.npm.taobao.org/eventsource-polyfill/download/eventsource-polyfill-0.9.6.tgz#10e0d187f111b167f28fdab918843ce7d818f13c" - integrity sha1-EODRh/ERsWfyj9q5GIQ859gY8Tw= - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.npm.taobao.org/evp_bytestokey/download/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI= - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/execa/download/execa-1.0.0.tgz?cache=0&sync_timestamp=1580995944411&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexeca%2Fdownload%2Fexeca-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha1-xiNqW7TfbW8V6I5/AXeYIWdJ3dg= - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^3.4.0: - version "3.4.0" - resolved "https://registry.npm.taobao.org/execa/download/execa-3.4.0.tgz?cache=0&sync_timestamp=1580995944411&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexeca%2Fdownload%2Fexeca-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" - integrity sha1-wI7UVQ72XYWPrCaf/IVyRG8364k= - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.npm.taobao.org/exit/download/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.npm.taobao.org/expand-template/download/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha1-bhSz/O4POmNA7LV9LokYaSBSpHw= - -express@^4.16.3: - version "4.17.1" - resolved "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ= - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo= - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.npm.taobao.org/extglob/download/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM= - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extract-css-chunks-webpack-plugin@^4.7.4: - version "4.7.4" - resolved "https://registry.npm.taobao.org/extract-css-chunks-webpack-plugin/download/extract-css-chunks-webpack-plugin-4.7.4.tgz#db6465ca18d1fe8a89bad3dc72803127638fb477" - integrity sha1-22RlyhjR/oqJutPccoAxJ2OPtHc= - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-external-import "^1.1.0-beta.3" - webpack-sources "^1.1.0" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha1-VFFFB3xQFJHjOxXsQIwpQ3bpSuQ= - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM= - -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.npm.taobao.org/figgy-pudding/download/figgy-pudding-3.5.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffiggy-pudding%2Fdownload%2Ffiggy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" - integrity sha1-tO7oFIq7Adzx0aw0Nn1Z4S+mHW4= - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.npm.taobao.org/figures/download/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha1-YlwYvSk8YE3EqN2y/r8MiDQXRq8= - dependencies: - escape-string-regexp "^1.0.5" - -file-loader@^4.3.0: - version "4.3.0" - resolved "https://registry.npm.taobao.org/file-loader/download/file-loader-4.3.0.tgz?cache=0&sync_timestamp=1584448526032&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffile-loader%2Fdownload%2Ffile-loader-4.3.0.tgz#780f040f729b3d18019f20605f723e844b8a58af" - integrity sha1-eA8ED3KbPRgBnyBgX3I+hEuKWK8= - dependencies: - loader-utils "^1.2.3" - schema-utils "^2.5.0" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/file-uri-to-path/download/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90= - -filesize@^3.6.1: - version "3.6.1" - resolved "https://registry.npm.taobao.org/filesize/download/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha1-CQuz7gG2+AGoqL6Z0xcQs0Irsxc= - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha1-GRmmp8df44ssfHflGYU12prN2kA= - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.1.2, finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0= - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-2.1.0.tgz?cache=0&sync_timestamp=1583735626956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc= - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.0.0, find-cache-dir@^3.2.0: - version "3.3.1" - resolved "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-3.3.1.tgz?cache=0&sync_timestamp=1583735626956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha1-ibM/rUpGcNqpT4Vff74x1thP6IA= - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/find-up/download/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha1-SRafHXmTQwZG2mHsxa41XCHJe3M= - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/find-up/download/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha1-l6/n1s3AvFkoWEt8jXsW6KmqXRk= - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flatten@^1.0.2: - version "1.0.3" - resolved "https://registry.npm.taobao.org/flatten/download/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha1-wSg6yfJ7Noq8HjbR/3sEUBowNWs= - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.npm.taobao.org/flush-write-stream/download/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha1-jdfYc6G6vCB9lOrQwuDkQnbr8ug= - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha1-e3qfmuov3/NnhqlP9kPtB/T/Xio= - dependencies: - debug "=3.1.0" - -follow-redirects@^1.0.0: - version "1.11.0" - resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" - integrity sha1-r6FPCLoSpSljFA/kMhJliJe8Dss= - dependencies: - debug "^3.0.0" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/for-in/download/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha1-3M5SwF9kTymManq5Nr1yTO/786Y= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.npm.taobao.org/fragment-cache/download/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2, fresh@^0.5.2: - version "0.5.2" - resolved "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.npm.taobao.org/from2/download/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/fs-constants/download/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha1-a+Dem+mYzhavivwkSXue6bfM2a0= - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.npm.taobao.org/fs-extra/download/fs-extra-8.1.0.tgz?cache=0&sync_timestamp=1584632171258&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffs-extra%2Fdownload%2Ffs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha1-SdQ8RaiM2Wd2aMt74bRu/bjS4cA= - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/fs-minipass/download/fs-minipass-2.1.0.tgz?cache=0&sync_timestamp=1579628715553&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffs-minipass%2Fdownload%2Ffs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha1-f1A2/b8SxjwWkZDL5BmchSJx+fs= - dependencies: - minipass "^3.0.0" - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.npm.taobao.org/fs-write-stream-atomic/download/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.12" - resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" - integrity sha1-234NjsOwtFck/U2D1DVUqPHw3lw= - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.2" - resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha1-TAofs0vGjlQ7S4Kp7Dkr+9qECAU= - -fstream@^1.0.0, fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.npm.taobao.org/fstream/download/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha1-Touo7i1Ivk99DeUFRVVI6uWTIEU= - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0= - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.npm.taobao.org/gauge/download/gauge-2.7.4.tgz?cache=0&sync_timestamp=1580507586314&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgauge%2Fdownload%2Fgauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.npm.taobao.org/gaze/download/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha1-xEFzPhO5J6yMD/C0w7Az8ogSkko= - dependencies: - globule "^1.0.0" - -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.npm.taobao.org/gensync/download/gensync-1.0.0-beta.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgensync%2Fdownload%2Fgensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha1-WPQ2H/mH5f9uHnohCCeqNx6qwmk= - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.npm.taobao.org/get-caller-file/download/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o= - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/get-stdin/download/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/get-stream/download/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha1-wbJVV189wh1Zv8ec09K0axw6VLU= - dependencies: - pump "^3.0.0" - -get-stream@^5.0.0: - version "5.1.0" - resolved "https://registry.npm.taobao.org/get-stream/download/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha1-ASA83JJZf5uQkGfD5lbMH008Tck= - dependencies: - pump "^3.0.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.npm.taobao.org/get-value/download/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.npm.taobao.org/getpass/download/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.npm.taobao.org/github-from-package/download/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/glob-parent/download/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.npm.taobao.org/glob-parent/download/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha1-tsHvQXxOVmPqSY8cRa+saRa7wik= - dependencies: - is-glob "^4.0.1" - -glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: - version "7.1.6" - resolved "https://registry.npm.taobao.org/glob/download/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha1-FB8zuBp8JJLhJVlDB0gMRmeSeKY= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npm.taobao.org/globals/download/globals-11.12.0.tgz?cache=0&sync_timestamp=1583747623625&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fglobals%2Fdownload%2Fglobals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4= - -globule@^1.0.0: - version "1.3.1" - resolved "https://registry.npm.taobao.org/globule/download/globule-1.3.1.tgz#90a25338f22b7fbeb527cee63c629aea754d33b9" - integrity sha1-kKJTOPIrf761J87mPGKa6nVNM7k= - dependencies: - glob "~7.1.1" - lodash "~4.17.12" - minimatch "~3.0.2" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: - version "4.2.3" - resolved "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha1-ShL/G2A3bvCYYsIJPt2Qgyi+hCM= - -gzip-size@^5.0.0: - version "5.1.1" - resolved "https://registry.npm.taobao.org/gzip-size/download/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha1-y5vuaS+HwGErIyhAqHOQTkwTUnQ= - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - -hable@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/hable/download/hable-3.0.0.tgz#6de089b2df946635cf8134b9e4859f1b62de255f" - integrity sha1-beCJst+UZjXPgTS55IWfG2LeJV8= - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.npm.taobao.org/har-validator/download/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha1-HvievT5JllV2de7ZiTEQ3DUPoIA= - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - -hard-source-webpack-plugin@^0.13.1: - version "0.13.1" - resolved "https://registry.npm.taobao.org/hard-source-webpack-plugin/download/hard-source-webpack-plugin-0.13.1.tgz#a99071e25b232f1438a5bc3c99f10a3869e4428e" - integrity sha1-qZBx4lsjLxQ4pbw8mfEKOGnkQo4= - dependencies: - chalk "^2.4.1" - find-cache-dir "^2.0.0" - graceful-fs "^4.1.11" - lodash "^4.15.0" - mkdirp "^0.5.1" - node-object-hash "^1.2.0" - parse-json "^4.0.0" - pkg-dir "^3.0.0" - rimraf "^2.6.2" - semver "^5.6.0" - tapable "^1.0.0-beta.5" - webpack-sources "^1.0.1" - write-json-file "^2.3.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/has-ansi/download/has-ansi-2.0.0.tgz?cache=0&sync_timestamp=1573220226399&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-ansi%2Fdownload%2Fhas-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s= - -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/has-symbols/download/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha1-n1IUdYpEGWxAbZvXbOv4HsLdMeg= - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/has-unicode/download/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.npm.taobao.org/has-value/download/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/has-value/download/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.npm.taobao.org/has-values/download/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/has-values/download/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.0, has@^1.0.3: - version "1.0.3" - resolved "https://registry.npm.taobao.org/has/download/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y= - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.npm.taobao.org/hash-base/download/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash-sum@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/hash-sum/download/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" - integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ= - -hash-sum@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/hash-sum/download/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" - integrity sha1-gdAbtd6OpKIUrV1urRtSNGCwtFo= - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hasha@^5.0.0: - version "5.2.0" - resolved "https://registry.npm.taobao.org/hasha/download/hasha-5.2.0.tgz#33094d1f69c40a4a6ac7be53d5fe3ff95a269e0c" - integrity sha1-MwlNH2nECkpqx75T1f4/+Vomngw= - dependencies: - is-stream "^2.0.0" - type-fest "^0.8.0" - -he@1.2.x, he@^1.1.0, he@^1.2.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/he/download/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha1-hK5l+n6vsWX922FWauFLrwVmTw8= - -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/hex-color-regex/download/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4= - -highlight.js@*: - version "9.18.1" - resolved "https://registry.npm.taobao.org/highlight.js/download/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c" - integrity sha1-7SGqAB/mJSuxCj121HVzxlOf4Tw= - -hmac-drbg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hoopy@^0.1.4: - version "0.1.4" - resolved "https://registry.npm.taobao.org/hoopy/download/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" - integrity sha1-YJIH1mEQADOpqUAq096mdzgcGx0= - -hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha1-dTm9S8Hg4KiVgVouAmJCCxKFhIg= - -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/hsl-regex/download/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/hsla-regex/download/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.npm.taobao.org/html-comment-regex/download/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha1-l9RoiutcgYhqNk+qDK0d2hTUM6c= - -html-entities@^1.2.0: - version "1.3.1" - resolved "https://registry.npm.taobao.org/html-entities/download/html-entities-1.3.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-entities%2Fdownload%2Fhtml-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha1-+5oaS1sUxdq6gtPjTGrk/nAaDkQ= - -html-minifier@^3.2.3: - version "3.5.21" - resolved "https://registry.npm.taobao.org/html-minifier/download/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" - integrity sha1-0AQOBUcw41TbAIRjWTGUAVIS0gw= - dependencies: - camel-case "3.0.x" - clean-css "4.2.x" - commander "2.17.x" - he "1.2.x" - param-case "2.1.x" - relateurl "0.2.x" - uglify-js "3.4.x" - -html-minifier@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/html-minifier/download/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" - integrity sha1-zKmq2LzhF14C4XqMM+RtiYiIn1Y= - dependencies: - camel-case "^3.0.0" - clean-css "^4.2.1" - commander "^2.19.0" - he "^1.2.0" - param-case "^2.1.1" - relateurl "^0.2.7" - uglify-js "^3.5.1" - -html-tags@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/html-tags/download/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" - integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= - -html-webpack-plugin@^3.2.0: - version "3.2.0" - resolved "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-3.2.0.tgz?cache=0&sync_timestamp=1586450111527&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-webpack-plugin%2Fdownload%2Fhtml-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" - integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s= - dependencies: - html-minifier "^3.2.3" - loader-utils "^0.2.16" - lodash "^4.17.3" - pretty-error "^2.0.2" - tapable "^1.0.0" - toposort "^1.0.0" - util.promisify "1.0.0" - -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.npm.taobao.org/htmlparser2/download/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha1-vWedw/WYl7ajS7EHSchVu1OpOS8= - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha1-bGGeT5xgMIw4UZSYwU+7EKrOuwY= - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-proxy-middleware@^0.19.1: - version "0.19.1" - resolved "https://registry.npm.taobao.org/http-proxy-middleware/download/http-proxy-middleware-0.19.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-proxy-middleware%2Fdownload%2Fhttp-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha1-GDx9xKoUeRUDBkmMIQza+WCApDo= - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - -http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.npm.taobao.org/http-proxy/download/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" - integrity sha1-2+VfY+daNH2389mZdPJpKjFKajo= - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-signature%2Fdownload%2Fhttp-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/human-signals/download/human-signals-1.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhuman-signals%2Fdownload%2Fhuman-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha1-xbHNFPUK6uCatsWf5jujOV/k36M= - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs= - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.npm.taobao.org/icss-utils/download/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha1-IRcLU3ie4nRHwvR91oMIFAP5pGc= - dependencies: - postcss "^7.0.14" - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.npm.taobao.org/iferr/download/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= - -ignore@^5.1.4: - version "5.1.4" - resolved "https://registry.npm.taobao.org/ignore/download/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" - integrity sha1-hLez2+ZFUrbvDsqZ9nQ9vsbZet8= - -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/import-cwd/download/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/import-fresh/download/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/import-from/download/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -in-publish@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/in-publish/download/in-publish-2.0.1.tgz?cache=0&sync_timestamp=1584388504097&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fin-publish%2Fdownload%2Fin-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" - integrity sha1-lIsaU1yAMFYc6lIvc/ePS+NX4Aw= - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/indent-string/download/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/indent-string/download/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha1-Yk+PRJfWGbLZdoUx1Y9BIoVNclE= - -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/indexes-of/download/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.npm.taobao.org/infer-owner/download/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha1-xM78qo5RBRwqQLos6KPScpWvlGc= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc= - -invariant@^2.2.2, invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.npm.taobao.org/invariant/download/invariant-2.2.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finvariant%2Fdownload%2Finvariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY= - dependencies: - loose-envify "^1.0.0" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/invert-kv/download/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.npm.taobao.org/ip/download/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha1-v/OFQ+64mEglB5/zoqjmy9RngbM= - -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/is-absolute-url/download/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY= - dependencies: - kind-of "^6.0.0" - -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.npm.taobao.org/is-arguments/download/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha1-P6+WbHy6D/Q3+zH2JQCC/PBEjPM= - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha1-RXSirlb3qyBolvtDHq7tBm/fjwM= - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk= - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha1-76ouqdqg16suoTqXsritUf776L4= - -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.npm.taobao.org/is-callable/download/is-callable-1.1.5.tgz?cache=0&sync_timestamp=1576778345881&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha1-9+RrWWiQRW23Tn9ul2yzJz0G+qs= - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/is-color-stop/download/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= - dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc= - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/is-date-object/download/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha1-vac28s2P0G0yhE53Q7+nSUw7/X4= - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco= - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw= - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.npm.taobao.org/is-directory/download/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.npm.taobao.org/is-extendable/download/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ= - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/is-finite/download/is-finite-1.1.0.tgz?cache=0&sync_timestamp=1581060993775&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-finite%2Fdownload%2Fis-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha1-kEE1x3+0LAZB1qobzbxNqo2ggvM= - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0= - -is-generator-function@^1.0.7: - version "1.0.7" - resolved "https://registry.npm.taobao.org/is-generator-function/download/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" - integrity sha1-0hMuUpuwAAp/gHlNS99c1eWBNSI= - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/is-glob/download/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw= - dependencies: - is-extglob "^2.1.1" - -is-nan@^1.2.1: - version "1.3.0" - resolved "https://registry.npm.taobao.org/is-nan/download/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03" - integrity sha1-hdH1SC9wUcIBn1ZzzOvbBvOw2wM= - dependencies: - define-properties "^1.1.3" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/is-number/download/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss= - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/is-obj/download/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha1-Rz+wXZc3BeP9liBUUBjKjiLvSYI= - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/is-plain-obj/download/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.npm.taobao.org/is-plain-object/download/is-plain-object-2.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-plain-object%2Fdownload%2Fis-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc= - dependencies: - isobject "^3.0.1" - -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/is-promise/download/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.npm.taobao.org/is-regex/download/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha1-OdWJo1i/GJZ/cmlnEguPwa7XTq4= - dependencies: - has "^1.0.3" - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/is-resolvable/download/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg= - -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/is-retry-allowed/download/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha1-13hIi9CkZmo76KFIK58rqv7eqLQ= - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/is-stream/download/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha1-venDJoDW+uBBKdasnZIc54FfeOM= - -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/is-svg/download/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha1-kyHb0pwhLlypnE+peUxxS8r6L3U= - dependencies: - html-comment-regex "^1.1.0" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.npm.taobao.org/is-symbol/download/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha1-OOEBS55jKb4N6dJKQU/XRB7GGTc= - dependencies: - has-symbols "^1.0.1" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/is-typedarray/download/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.npm.taobao.org/is-utf8/download/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/is-windows/download/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0= - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/is-wsl/download/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/isobject/download/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npm.taobao.org/isobject/download/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jest-worker@^25.1.0: - version "25.2.6" - resolved "https://registry.npm.taobao.org/jest-worker/download/jest-worker-25.2.6.tgz#d1292625326794ce187c38f51109faced3846c58" - integrity sha1-0SkmJTJnlM4YfDj1EQn6ztOEbFg= - dependencies: - merge-stream "^2.0.0" - supports-color "^7.0.0" - -js-base64@^2.1.8: - version "2.5.2" - resolved "https://registry.npm.taobao.org/js-base64/download/js-base64-2.5.2.tgz?cache=0&sync_timestamp=1581520546694&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjs-base64%2Fdownload%2Fjs-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209" - integrity sha1-MTtidN2nGPcU0AszMLuubjjpAgk= - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha1-GSA/tZmR35jjoocFDUZHzerzJJk= - -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc= - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npm.taobao.org/jsesc/download/jsesc-2.5.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsesc%2Fdownload%2Fjsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q= - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.npm.taobao.org/jsesc/download/jsesc-0.5.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsesc%2Fdownload%2Fjsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/json-parse-better-errors/download/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk= - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha1-afaofZUTq4u4/mO9sJecRI5oRmA= - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json5@^0.5.0: - version "0.5.1" - resolved "https://registry.npm.taobao.org/json5/download/json5-0.5.1.tgz?cache=0&sync_timestamp=1586045666090&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/json5/download/json5-1.0.1.tgz?cache=0&sync_timestamp=1586045666090&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4= - dependencies: - minimist "^1.2.0" - -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.npm.taobao.org/json5/download/json5-2.1.3.tgz?cache=0&sync_timestamp=1586045666090&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha1-ybD3+pIzv+WAf+ZvzzpWF+1ZfUM= - dependencies: - minimist "^1.2.5" - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/jsonfile/download/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha1-cpyR4thXt6QZofmqZWhcTDP1hF0= - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha1-B8BQNKbDSfoG4k+jWqdttFgM5N0= - -last-call-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/last-call-webpack-plugin/download/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" - integrity sha1-l0LfDhDjz0blwDgcLekNOnotdVU= - dependencies: - lodash "^4.17.5" - webpack-sources "^1.1.0" - -launch-editor-middleware@^2.2.1: - version "2.2.1" - resolved "https://registry.npm.taobao.org/launch-editor-middleware/download/launch-editor-middleware-2.2.1.tgz#e14b07e6c7154b0a4b86a0fd345784e45804c157" - integrity sha1-4UsH5scVSwpLhqD9NFeE5FgEwVc= - dependencies: - launch-editor "^2.2.1" - -launch-editor@^2.2.1: - version "2.2.1" - resolved "https://registry.npm.taobao.org/launch-editor/download/launch-editor-2.2.1.tgz#871b5a3ee39d6680fcc26d37930b6eeda89db0ca" - integrity sha1-hxtaPuOdZoD8wm03kwtu7aidsMo= - dependencies: - chalk "^2.3.0" - shell-quote "^1.6.1" - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/lcid/download/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/leven/download/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha1-d4kd6DQGTMy6gq54QrtrFKE+1/I= - -levenary@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/levenary/download/levenary-1.1.1.tgz?cache=0&sync_timestamp=1580182898995&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flevenary%2Fdownload%2Flevenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" - integrity sha1-hCqe6Y0gdap/ru2+MmeekgX0b3c= - dependencies: - leven "^3.1.0" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/load-json-file/download/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -loader-runner@^2.3.1, loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.npm.taobao.org/loader-runner/download/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha1-7UcGa/5TTX6ExMe5mYwqdWB9k1c= - -loader-utils@^0.2.16: - version "0.2.17" - resolved "https://registry.npm.taobao.org/loader-utils/download/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.npm.taobao.org/loader-utils/download/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha1-xXm140yzSxp07cbB+za/o3HVphM= - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loadjs@^4.2.0: - version "4.2.0" - resolved "https://registry.npm.taobao.org/loadjs/download/loadjs-4.2.0.tgz#2a0336376397a6a43edf98c9ec3229ddd5abb6f6" - integrity sha1-KgM2N2OXpqQ+35jJ7DIp3dWrtvY= - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4= - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha1-Gvujlq/WdqbUJQTQpno6frn2KqA= - dependencies: - p-locate "^4.1.0" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/lodash._reinterpolate/download/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - -lodash.kebabcase@^4.1.1: - version "4.1.1" - resolved "https://registry.npm.taobao.org/lodash.kebabcase/download/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" - integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.npm.taobao.org/lodash.memoize/download/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - -lodash.template@^4.5.0: - version "4.5.0" - resolved "https://registry.npm.taobao.org/lodash.template/download/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha1-+XYZXPPzR9DV9SSDVp/oAxzM6Ks= - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.npm.taobao.org/lodash.templatesettings/download/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha1-5IExDwSdPPbUfpEq0JMTsVTw+zM= - dependencies: - lodash._reinterpolate "^3.0.0" - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.npm.taobao.org/lodash.uniq/download/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.5, lodash@~4.17.12: - version "4.17.15" - resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.15.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg= - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8= - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.npm.taobao.org/loud-rejection/download/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.npm.taobao.org/lower-case/download/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= - -lru-cache@^4.0.1, lru-cache@^4.1.2: - version "4.1.5" - resolved "https://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80= - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.npm.taobao.org/lru-cache/download/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA= - dependencies: - yallist "^3.0.2" - -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-1.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmake-dir%2Fdownload%2Fmake-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha1-ecEDO4BRW9bSTsmTPoYMp17ifww= - dependencies: - pify "^3.0.0" - -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmake-dir%2Fdownload%2Fmake-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU= - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-3.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmake-dir%2Fdownload%2Fmake-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha1-BKGsvyIiHh1u9DVZ9D4FqQ27Q5I= - dependencies: - semver "^6.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.npm.taobao.org/map-cache/download/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/map-obj/download/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/map-visit/download/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.npm.taobao.org/md5.js/download/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha1-tdB7jjIW4+J81yjXL3DR5qNCAF8= - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.npm.taobao.org/mdn-data/download/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha1-aZs8OKxvHXKAkaZGULZdOIUC/Vs= - -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.npm.taobao.org/mdn-data/download/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha1-hS3GD8ql2qLoz2yRicRA7T4EKXg= - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.npm.taobao.org/memory-fs/download/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.npm.taobao.org/memory-fs/download/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha1-MkwBKIuIZSlm0WHbd4OHIIRajjw= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -meow@^3.7.0: - version "3.7.0" - resolved "https://registry.npm.taobao.org/meow/download/meow-3.7.0.tgz?cache=0&sync_timestamp=1584641212049&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmeow%2Fdownload%2Fmeow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -merge-source-map@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/merge-source-map/download/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" - integrity sha1-L93n5gIJOfcJBqaPLXrmheTIxkY= - dependencies: - source-map "^0.6.1" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/merge-stream/download/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha1-UoI2KaFN0AyXcPtq1H3GMQ8sH2A= - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.npm.taobao.org/micromatch/download/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha1-cIWbyVyYQJUvNZoGij/En57PrCM= - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.npm.taobao.org/miller-rabin/download/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha1-8IA1HIZbDcViqEYpZtqlNUPHik0= - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": - version "1.43.0" - resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.43.0.tgz?cache=0&sync_timestamp=1578281193492&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha1-ChLgUCZQ5HPXNVNQUOfI9OtPrlg= - -mime-types@^2.1.12, mime-types@^2.1.19, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.26.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-types%2Fdownload%2Fmime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha1-nJIfwJt+FJpl39wNpNIJlyALCgY= - dependencies: - mime-db "1.43.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE= - -mime@^2.3.1, mime@^2.4.4: - version "2.4.4" - resolved "https://registry.npm.taobao.org/mime/download/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha1-vXuRE1/GsBzePpuuM9ZZtj2IV+U= - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/mimic-fn/download/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs= - -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/mimic-response/download/mimic-response-2.1.0.tgz?cache=0&sync_timestamp=1581923542865&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmimic-response%2Fdownload%2Fmimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha1-0Tdj019hPQnsN+uzC6wEacDuj0M= - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@^3.0.4, minimatch@~3.0.2: - version "3.0.4" - resolved "https://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fminimatch%2Fdownload%2Fminimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM= - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI= - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/minipass-collect/download/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha1-IrgTv3Rdxu26JXa5QAIq1u3Ixhc= - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.npm.taobao.org/minipass-flush/download/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha1-gucTXX6JpQ/+ZGEKeHlTxMTLs3M= - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.2" - resolved "https://registry.npm.taobao.org/minipass-pipeline/download/minipass-pipeline-1.2.2.tgz#3dcb6bb4a546e32969c7ad710f2c79a86abba93a" - integrity sha1-PctrtKVG4ylpx61xDyx5qGq7qTo= - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.1.1" - resolved "https://registry.npm.taobao.org/minipass/download/minipass-3.1.1.tgz?cache=0&sync_timestamp=1573220239554&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fminipass%2Fdownload%2Fminipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" - integrity sha1-dgfOd4RyoYWtbYkIKqIHD3nO3NU= - dependencies: - yallist "^4.0.0" - -minizlib@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/minizlib/download/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" - integrity sha1-/VLGRTAe8JpjosIJaXwpTGzgLPM= - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/mississippi/download/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha1-6goykfl+C16HdrNj1fChLZTGcCI= - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.npm.taobao.org/mixin-deep/download/mixin-deep-1.3.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmixin-deep%2Fdownload%2Fmixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY= - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp-classic@^0.5.2: - version "0.5.2" - resolved "https://registry.npm.taobao.org/mkdirp-classic/download/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" - integrity sha1-VMRBzkyWzXeQ4QtBqHqlEGjsqys= - -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8= - dependencies: - minimist "^1.2.5" - -mkdirp@^1.0.3: - version "1.0.4" - resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha1-PrXtYmInVteaXw4qIh3+utdcL34= - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&sync_timestamp=1559842528856&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz?cache=0&sync_timestamp=1559842528856&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo= - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&sync_timestamp=1559842528856&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= - -mustache@^2.3.0: - version "2.3.2" - resolved "https://registry.npm.taobao.org/mustache/download/mustache-2.3.2.tgz?cache=0&sync_timestamp=1584325517042&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmustache%2Fdownload%2Fmustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" - integrity sha1-ptTZw/kdEzWauImoEpVPkjCj0MU= - -nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: - version "2.14.0" - resolved "https://registry.npm.taobao.org/nan/download/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha1-eBj3IgJ7JFmobwKV1DTR/CM2xSw= - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk= - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/napi-build-utils/download/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" - integrity sha1-sf3cCyxG44Cgt6dvmE3UfEGhOAY= - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs= - -neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: - version "2.6.1" - resolved "https://registry.npm.taobao.org/neo-async/download/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha1-rCetpmFn+ohJpq3dg39rGJrSCBw= - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.npm.taobao.org/nice-try/download/nice-try-1.0.5.tgz?cache=0&sync_timestamp=1584699756095&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnice-try%2Fdownload%2Fnice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y= - -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.npm.taobao.org/no-case/download/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw= - dependencies: - lower-case "^1.1.1" - -node-abi@^2.7.0: - version "2.15.0" - resolved "https://registry.npm.taobao.org/node-abi/download/node-abi-2.15.0.tgz#51d55cc711bd9e4a24a572ace13b9231945ccb10" - integrity sha1-UdVcxxG9nkokpXKs4TuSMZRcyxA= - dependencies: - semver "^5.4.1" - -node-fetch@^2.6.0: - version "2.6.0" - resolved "https://registry.npm.taobao.org/node-fetch/download/node-fetch-2.6.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-fetch%2Fdownload%2Fnode-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha1-5jNFY4bUqlWGP2dqerDaqP3ssP0= - -node-gyp@^3.8.0: - version "3.8.0" - resolved "https://registry.npm.taobao.org/node-gyp/download/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" - integrity sha1-VAMEJhwzDoDQ1e3OJTpoyzlkIYw= - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "^2.87.0" - rimraf "2" - semver "~5.3.0" - tar "^2.0.0" - which "1" - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.npm.taobao.org/node-libs-browser/download/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha1-tk9RPRgzhiX5A0bSew0jXmMfZCU= - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-object-hash@^1.2.0: - version "1.4.2" - resolved "https://registry.npm.taobao.org/node-object-hash/download/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94" - integrity sha1-OFgz2FsimQK3WCYiT2B3vpaanpQ= - -node-releases@^1.1.53: - version "1.1.53" - resolved "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.53.tgz?cache=0&sync_timestamp=1585406790332&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" - integrity sha1-LYIb+kme18Xf/F4vKMiOeKCO4/Q= - -node-res@^5.0.1: - version "5.0.1" - resolved "https://registry.npm.taobao.org/node-res/download/node-res-5.0.1.tgz#ffaa462e206509d66d0ba28a4daf1f032daa6460" - integrity sha1-/6pGLiBlCdZtC6KKTa8fAy2qZGA= - dependencies: - destroy "^1.0.4" - etag "^1.8.1" - mime-types "^2.1.19" - on-finished "^2.3.0" - vary "^1.1.2" - -node-sass@^4.13.1: - version "4.13.1" - resolved "https://registry.npm.taobao.org/node-sass/download/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" - integrity sha1-nbVolpa7LuwsMrmL/qTHoumS0KM= - dependencies: - async-foreach "^0.1.3" - chalk "^1.1.1" - cross-spawn "^3.0.0" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - in-publish "^2.0.0" - lodash "^4.17.15" - meow "^3.7.0" - mkdirp "^0.5.1" - nan "^2.13.2" - node-gyp "^3.8.0" - npmlog "^4.0.0" - request "^2.88.0" - sass-graph "^2.2.4" - stdout-stream "^1.4.0" - "true-case-path" "^1.0.2" - -noop-logger@^0.1.1: - version "0.1.1" - resolved "https://registry.npm.taobao.org/noop-logger/download/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" - integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= - -"nopt@2 || 3": - version "3.0.6" - resolved "https://registry.npm.taobao.org/nopt/download/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= - dependencies: - abbrev "1" - -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.5.0" - resolved "https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha1-5m2xg4sgDB38IzIl0SyzZSDiNKg= - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/normalize-path/download/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU= - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.npm.taobao.org/normalize-range/download/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.npm.taobao.org/normalize-url/download/normalize-url-1.9.1.tgz?cache=0&sync_timestamp=1580491504079&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnormalize-url%2Fdownload%2Fnormalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.npm.taobao.org/normalize-url/download/normalize-url-3.3.0.tgz?cache=0&sync_timestamp=1580491504079&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnormalize-url%2Fdownload%2Fnormalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha1-suHE3E98bVd0PfczpPWXjRhlBVk= - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.0: - version "4.0.1" - resolved "https://registry.npm.taobao.org/npm-run-path/download/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha1-t+zR5e1T2o43pV4cImnguX7XSOo= - dependencies: - path-key "^3.0.0" - -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.npm.taobao.org/npmlog/download/npmlog-4.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnpmlog%2Fdownload%2Fnpmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha1-CKfyqL9zRgR3mp76StXMcXq7lUs= - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -nth-check@^1.0.2, nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/nth-check/download/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw= - dependencies: - boolbase "~1.0.0" - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.npm.taobao.org/num2fraction/download/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -nuxt@^2.0.0: - version "2.12.2" - resolved "https://registry.npm.taobao.org/nuxt/download/nuxt-2.12.2.tgz#959baa919bfef4adcd9e88f84bc948a49a03edd8" - integrity sha1-lZuqkZv+9K3Nnoj4S8lIpJoD7dg= - dependencies: - "@nuxt/builder" "2.12.2" - "@nuxt/cli" "2.12.2" - "@nuxt/core" "2.12.2" - "@nuxt/generator" "2.12.2" - "@nuxt/loading-screen" "^1.2.0" - "@nuxt/opencollective" "^0.3.0" - "@nuxt/webpack" "2.12.2" - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU= - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fobject-assign%2Fdownload%2Fobject-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.npm.taobao.org/object-copy/download/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.npm.taobao.org/object-inspect/download/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha1-9Pa9GBrXfwBrXs5gvQtvOY/3Smc= - -object-is@^1.0.1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/object-is/download/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha1-a4DrhP5FFJj2UAeYLwNaW0Re3sQ= - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/object-keys/download/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha1-HEfyct8nfzsdrwYWd9nILiMixg4= - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/object-visit/download/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/object.assign/download/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha1-lovxEA15Vrs8oIbwBvhGs7xACNo= - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/object.getownpropertydescriptors/download/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha1-Npvx+VktiridcS3O1cuBx8U1Jkk= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.npm.taobao.org/object.pick/download/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0: - version "1.1.1" - resolved "https://registry.npm.taobao.org/object.values/download/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha1-aKmezeNWt+kpWjxeDOMdyMlT3l4= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" - -on-finished@^2.3.0, on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -on-headers@^1.0.2, on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/on-headers/download/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8= - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npm.taobao.org/once/download/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.npm.taobao.org/onetime/download/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha1-//DzyRYX/mK7UBiWNumayKbfe+U= - dependencies: - mimic-fn "^2.1.0" - -opener@1.5.1, opener@^1.5.1: - version "1.5.1" - resolved "https://registry.npm.taobao.org/opener/download/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" - integrity sha1-bS8Od/GgrwAyrKcWwsH7uOfoq+0= - -optimize-css-assets-webpack-plugin@^5.0.3: - version "5.0.3" - resolved "https://registry.npm.taobao.org/optimize-css-assets-webpack-plugin/download/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572" - integrity sha1-4vHU2UrYwK+JZ+vXzxONyx7xRXI= - dependencies: - cssnano "^4.1.10" - last-call-webpack-plugin "^3.0.0" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.npm.taobao.org/os-browserify/download/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/os-homedir/download/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.npm.taobao.org/os-locale/download/os-locale-1.4.0.tgz?cache=0&sync_timestamp=1584865955375&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fos-locale%2Fdownload%2Fos-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/os-tmpdir/download/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@0: - version "0.1.5" - resolved "https://registry.npm.taobao.org/osenv/download/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha1-hc36+uso6Gd/QW4odZK18/SepBA= - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz?cache=0&sync_timestamp=1560983840673&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-finally%2Fdownload%2Fp-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-2.0.1.tgz?cache=0&sync_timestamp=1560983840673&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-finally%2Fdownload%2Fp-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha1-vW/KqcVZoJa2gIBvTWV7Pw8kBWE= - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.npm.taobao.org/p-limit/download/p-limit-1.3.0.tgz?cache=0&sync_timestamp=1586101408834&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-limit%2Fdownload%2Fp-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha1-uGvV8MJWkJEcdZD8v8IBDVSzzLg= - dependencies: - p-try "^1.0.0" - -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: - version "2.3.0" - resolved "https://registry.npm.taobao.org/p-limit/download/p-limit-2.3.0.tgz?cache=0&sync_timestamp=1586101408834&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-limit%2Fdownload%2Fp-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE= - dependencies: - p-try "^2.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/p-locate/download/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ= - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/p-locate/download/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha1-o0KLtwiLOmApL2aRkni3wpetTwc= - dependencies: - p-limit "^2.2.0" - -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/p-map/download/p-map-3.0.0.tgz?cache=0&sync_timestamp=1583398406446&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-map%2Fdownload%2Fp-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha1-1wTZr4orpoTiYA2aIVmD1BQal50= - dependencies: - aggregate-error "^3.0.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha1-yyhoVA4xPWHeWPr741zpAE1VQOY= - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.npm.taobao.org/pako/download/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8= - -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/parallel-transform/download/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha1-kEnKN9bLIYLDsdLHIL6U0UpYFPw= - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -param-case@2.1.x, param-case@^2.1.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/param-case/download/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" - integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= - dependencies: - no-case "^2.2.0" - -parse-asn1@^5.0.0: - version "5.1.5" - resolved "https://registry.npm.taobao.org/parse-asn1/download/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha1-ADJxND2ljclMrOSU+u89IUfs6g4= - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/parse-json/download/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ= - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.npm.taobao.org/pascalcase/download/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.npm.taobao.org/path-browserify/download/path-browserify-0.0.1.tgz?cache=0&sync_timestamp=1583254548523&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpath-browserify%2Fdownload%2Fpath-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha1-5sTd1+06onxoogzE5Q4aTug7vEo= - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.npm.taobao.org/path-dirname/download/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/path-exists/download/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/path-exists/download/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha1-UTvb4tO5XXdi6METfvoZXGxhtbM= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/path-key/download/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npm.taobao.org/path-key/download/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U= - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha1-1i27VnlAXXLEc37FhgDp3c8G0kw= - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/path-type/download/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -pbkdf2@^3.0.3: - version "3.0.17" - resolved "https://registry.npm.taobao.org/pbkdf2/download/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" - integrity sha1-l2wgZTBhexTrsyEUI597CTNuk6Y= - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -picomatch@^2.0.4, picomatch@^2.0.7: - version "2.2.2" - resolved "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz?cache=0&sync_timestamp=1584791322335&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpicomatch%2Fdownload%2Fpicomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha1-IfMz6ba46v8CRo9RRupAbTRfTa0= - -pify@^2.0.0, pify@^2.3.0: - version "2.3.0" - resolved "https://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz?cache=0&sync_timestamp=1581725110840&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpify%2Fdownload%2Fpify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz?cache=0&sync_timestamp=1581725110840&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpify%2Fdownload%2Fpify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/pify/download/pify-4.0.1.tgz?cache=0&sync_timestamp=1581725110840&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpify%2Fdownload%2Fpify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE= - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM= - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha1-8JkTPfft5CLoHR2ESCcO6z5CYfM= - dependencies: - find-up "^4.0.0" - -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/pkg-up/download/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= - dependencies: - find-up "^2.1.0" - -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/pkg-up/download/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha1-EA7CNcwVDk/UJRlBJZaihRKg3vU= - dependencies: - find-up "^3.0.0" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.npm.taobao.org/posix-character-classes/download/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -postcss-attribute-case-insensitive@^4.0.1: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-attribute-case-insensitive/download/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" - integrity sha1-2T5GtQRYnpSscnewRjImxoBBqIA= - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^6.0.2" - -postcss-calc@^7.0.1: - version "7.0.2" - resolved "https://registry.npm.taobao.org/postcss-calc/download/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" - integrity sha1-UE780AjKAnMSBWiweSsWzc3oqsE= - dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-color-functional-notation@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/postcss-color-functional-notation/download/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" - integrity sha1-Xv03qI+6vrAKKWbR5T2Yztk/dOA= - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-gray@^5.0.0: - version "5.0.0" - resolved "https://registry.npm.taobao.org/postcss-color-gray/download/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" - integrity sha1-Uyox65CfjaiYzv/ilv3B+GS+hUc= - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-color-hex-alpha@^5.0.3: - version "5.0.3" - resolved "https://registry.npm.taobao.org/postcss-color-hex-alpha/download/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" - integrity sha1-qNnKTDnUl8lmHjdLnFGJnvD4c4g= - dependencies: - postcss "^7.0.14" - postcss-values-parser "^2.0.1" - -postcss-color-mod-function@^3.0.3: - version "3.0.3" - resolved "https://registry.npm.taobao.org/postcss-color-mod-function/download/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" - integrity sha1-gWuhRawRzDy2uqkFp1pJ+QPk0x0= - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-rebeccapurple@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-color-rebeccapurple/download/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" - integrity sha1-x6ib6HK7dORbHjAiv+V0iCPm3nc= - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.npm.taobao.org/postcss-colormin/download/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha1-rgYLzpPteUrHEmTwgTLVUJVr04E= - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-convert-values/download/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8= - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-custom-media@^7.0.8: - version "7.0.8" - resolved "https://registry.npm.taobao.org/postcss-custom-media/download/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" - integrity sha1-//0T/+/61zYhvl84cHaiiwApTgw= - dependencies: - postcss "^7.0.14" - -postcss-custom-properties@^8.0.11: - version "8.0.11" - resolved "https://registry.npm.taobao.org/postcss-custom-properties/download/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" - integrity sha1-LWF3LW6S8i9eDVJgLfj65G+jDZc= - dependencies: - postcss "^7.0.17" - postcss-values-parser "^2.0.1" - -postcss-custom-selectors@^5.1.2: - version "5.1.2" - resolved "https://registry.npm.taobao.org/postcss-custom-selectors/download/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" - integrity sha1-ZIWMbrLs/y+0HQsoyd17PbTef7o= - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-dir-pseudo-class@^5.0.0: - version "5.0.0" - resolved "https://registry.npm.taobao.org/postcss-dir-pseudo-class/download/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" - integrity sha1-bjpBd9Dts6vMhf22+7HCbauuq6I= - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-discard-comments/download/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha1-H7q9LCRr/2qq15l7KwkY9NevQDM= - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-discard-duplicates/download/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha1-P+EzzTyCKC5VD8myORdqkge3hOs= - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-discard-empty/download/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha1-yMlR6fc+2UKAGUWERKAq2Qu592U= - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-discard-overridden/download/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha1-ZSrvipZybwKfXj4AFG7npOdV/1c= - dependencies: - postcss "^7.0.0" - -postcss-double-position-gradients@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/postcss-double-position-gradients/download/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" - integrity sha1-/JJ9Uv3ciWyzooEuvF3xR+EQUi4= - dependencies: - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-env-function@^2.0.2: - version "2.0.2" - resolved "https://registry.npm.taobao.org/postcss-env-function/download/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" - integrity sha1-Dz49PFfwlKksK69LYkHwsNpTZdc= - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-focus-visible@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/postcss-focus-visible/download/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" - integrity sha1-R30QcROt5gJLFBKDF63ivR4XBG4= - dependencies: - postcss "^7.0.2" - -postcss-focus-within@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/postcss-focus-within/download/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" - integrity sha1-djuHiFls7puHTJmSAc3egGWe9oA= - dependencies: - postcss "^7.0.2" - -postcss-font-variant@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/postcss-font-variant/download/postcss-font-variant-4.0.0.tgz#71dd3c6c10a0d846c5eda07803439617bbbabacc" - integrity sha1-cd08bBCg2EbF7aB4A0OWF7u6usw= - dependencies: - postcss "^7.0.2" - -postcss-gap-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/postcss-gap-properties/download/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" - integrity sha1-QxwZKrPtlqPD0J8v9hWWD5AsFxU= - dependencies: - postcss "^7.0.2" - -postcss-image-set-function@^3.0.1: - version "3.0.1" - resolved "https://registry.npm.taobao.org/postcss-image-set-function/download/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" - integrity sha1-KJIKLymUW+1MMZjX32SW1BDT8og= - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-import-resolver@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/postcss-import-resolver/download/postcss-import-resolver-2.0.0.tgz#95c61ac5489047bd93ff42a9cd405cfe9041e2c0" - integrity sha1-lcYaxUiQR72T/0KpzUBc/pBB4sA= - dependencies: - enhanced-resolve "^4.1.1" - -postcss-import@^12.0.1: - version "12.0.1" - resolved "https://registry.npm.taobao.org/postcss-import/download/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" - integrity sha1-z4x6sLXMq1ZJAkU25WX4QZKLcVM= - dependencies: - postcss "^7.0.1" - postcss-value-parser "^3.2.3" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-initial@^3.0.0: - version "3.0.2" - resolved "https://registry.npm.taobao.org/postcss-initial/download/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" - integrity sha1-8BhWNpSzwWro6qvjxYWsYxljey0= - dependencies: - lodash.template "^4.5.0" - postcss "^7.0.2" - -postcss-lab-function@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/postcss-lab-function/download/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" - integrity sha1-u1GmhWzRIomrSuINseOCHvE9fS4= - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/postcss-load-config/download/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha1-yE1pK3u3tB3c7ZTuYuirMbQXsAM= - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/postcss-loader/download/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha1-a5eUPkfHLYRfqeA/Jzdz1OjdbC0= - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" - -postcss-logical@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/postcss-logical/download/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" - integrity sha1-JJXQ+LgunyYnJfdflAGzTntF1bU= - dependencies: - postcss "^7.0.2" - -postcss-media-minmax@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/postcss-media-minmax/download/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" - integrity sha1-t1u2y8IXyKxJQz4S8iBIgUpPXtU= - dependencies: - postcss "^7.0.2" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.npm.taobao.org/postcss-merge-longhand/download/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha1-YvSaE+Sg7gTnuY9CuxYGLKJUniQ= - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.npm.taobao.org/postcss-merge-rules/download/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha1-NivqT/Wh+Y5AdacTxsslrv75plA= - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-minify-font-values/download/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY= - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-minify-gradients/download/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha1-k7KcL/UJnFNe7NpWxKpuZlpmNHE= - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-minify-params/download/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha1-a5zvAwwR41Jh+V9hjJADbWgNuHQ= - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-minify-selectors/download/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha1-4uXrQL/uUA0M2SQ1APX46kJi+9g= - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/postcss-modules-extract-imports/download/postcss-modules-extract-imports-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-modules-extract-imports%2Fdownload%2Fpostcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" - integrity sha1-gYcZoa4doyX5gyRGsBE27rSTzX4= - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/postcss-modules-local-by-default/download/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" - integrity sha1-6KZWG+kUqvPAUodjd1JMqQ27eRU= - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.16" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" - -postcss-modules-scope@^2.2.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/postcss-modules-scope/download/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" - integrity sha1-OFyuATzHdD9afXYC0Qc6iequYu4= - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/postcss-modules-values/download/postcss-modules-values-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-modules-values%2Fdownload%2Fpostcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" - integrity sha1-W1AA1uuuKbQlUwG0o6VFdEI+fxA= - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-nesting@^7.0.0: - version "7.0.1" - resolved "https://registry.npm.taobao.org/postcss-nesting/download/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" - integrity sha1-tQrXt/AXPlteOIDDUBNEcD4EwFI= - dependencies: - postcss "^7.0.2" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-normalize-charset/download/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha1-izWt067oOhNrBHHg1ZvlilAoXdQ= - dependencies: - postcss "^7.0.0" - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-normalize-display-values/download/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha1-Db4EpM6QY9RmftK+R2u4MMglk1o= - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-normalize-positions/download/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha1-BfdX+E8mBDc3g2ipH4ky1LECkX8= - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-normalize-repeat-style/download/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha1-xOu8KJ85kaAo1EdRy90RkYsXkQw= - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-normalize-string/download/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha1-zUTECrB6DHo23F6Zqs4eyk7CaQw= - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-normalize-timing-functions/download/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha1-jgCcoqOUnNr4rSPmtquZy159KNk= - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-normalize-unicode/download/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs= - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-normalize-url/download/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE= - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-normalize-whitespace/download/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha1-vx1AcP5Pzqh9E0joJdjMDF+qfYI= - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.npm.taobao.org/postcss-ordered-values/download/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha1-DPdcgg7H1cTSgBiVWeC1ceusDu4= - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-overflow-shorthand@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/postcss-overflow-shorthand/download/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" - integrity sha1-MezzUOnG9t3CUKePDD4RHzLdTDA= - dependencies: - postcss "^7.0.2" - -postcss-page-break@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/postcss-page-break/download/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" - integrity sha1-rdUtDgpSjKvmr+6LRuKrsnffRr8= - dependencies: - postcss "^7.0.2" - -postcss-place@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-place/download/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" - integrity sha1-6fOdM9LcWE5G7h20Wtt3yp0dzGI= - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-preset-env@^6.7.0: - version "6.7.0" - resolved "https://registry.npm.taobao.org/postcss-preset-env/download/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" - integrity sha1-w03az4+QI4OzWtHgMPF49M3xGKU= - dependencies: - autoprefixer "^9.6.1" - browserslist "^4.6.4" - caniuse-lite "^1.0.30000981" - css-blank-pseudo "^0.1.4" - css-has-pseudo "^0.10.0" - css-prefers-color-scheme "^3.1.1" - cssdb "^4.4.0" - postcss "^7.0.17" - postcss-attribute-case-insensitive "^4.0.1" - postcss-color-functional-notation "^2.0.1" - postcss-color-gray "^5.0.0" - postcss-color-hex-alpha "^5.0.3" - postcss-color-mod-function "^3.0.3" - postcss-color-rebeccapurple "^4.0.1" - postcss-custom-media "^7.0.8" - postcss-custom-properties "^8.0.11" - postcss-custom-selectors "^5.1.2" - postcss-dir-pseudo-class "^5.0.0" - postcss-double-position-gradients "^1.0.0" - postcss-env-function "^2.0.2" - postcss-focus-visible "^4.0.0" - postcss-focus-within "^3.0.0" - postcss-font-variant "^4.0.0" - postcss-gap-properties "^2.0.0" - postcss-image-set-function "^3.0.1" - postcss-initial "^3.0.0" - postcss-lab-function "^2.0.1" - postcss-logical "^3.0.0" - postcss-media-minmax "^4.0.0" - postcss-nesting "^7.0.0" - postcss-overflow-shorthand "^2.0.0" - postcss-page-break "^2.0.0" - postcss-place "^4.0.1" - postcss-pseudo-class-any-link "^6.0.0" - postcss-replace-overflow-wrap "^3.0.0" - postcss-selector-matches "^4.0.0" - postcss-selector-not "^4.0.0" - -postcss-pseudo-class-any-link@^6.0.0: - version "6.0.0" - resolved "https://registry.npm.taobao.org/postcss-pseudo-class-any-link/download/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" - integrity sha1-LtPu05OzcCh53sSocDKyENrrBNE= - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.npm.taobao.org/postcss-reduce-initial/download/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha1-f9QuvqXpyBRgljniwuhK4nC6SN8= - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-reduce-transforms/download/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha1-F++kBerMbge+NBSlyi0QdGgdTik= - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-replace-overflow-wrap@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/postcss-replace-overflow-wrap/download/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" - integrity sha1-YbNg/9rtyoTHyRjSsPDQ6lWasBw= - dependencies: - postcss "^7.0.2" - -postcss-selector-matches@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/postcss-selector-matches/download/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" - integrity sha1-ccgkj5F7osyTA3yWN+4JxkQ2/P8= - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-not@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/postcss-selector-not/download/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" - integrity sha1-xo/3upZSdJnoMnJKJnTWVgO2RcA= - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-3.1.2.tgz?cache=0&sync_timestamp=1582039942807&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-selector-parser%2Fdownload%2Fpostcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha1-sxD1xMD9r3b5SQK7qjDbaqhPUnA= - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-5.0.0.tgz?cache=0&sync_timestamp=1582039942807&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-selector-parser%2Fdownload%2Fpostcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha1-JJBENWaXsztk8aj3yAki3d7nGVw= - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-6.0.2.tgz?cache=0&sync_timestamp=1582039942807&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-selector-parser%2Fdownload%2Fpostcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha1-k0z3mdAWyDQRhZ4J3Oyt4BKG7Fw= - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.npm.taobao.org/postcss-svgo/download/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha1-F7mXvHEbMzurFDqu07jT1uPTglg= - dependencies: - is-svg "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" - -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.npm.taobao.org/postcss-unique-selectors/download/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w= - dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-url@^8.0.0: - version "8.0.0" - resolved "https://registry.npm.taobao.org/postcss-url/download/postcss-url-8.0.0.tgz#7b10059bd12929cdbb1971c60f61a0e5af86b4ca" - integrity sha1-exAFm9EpKc27GXHGD2Gg5a+GtMo= - dependencies: - mime "^2.3.1" - minimatch "^3.0.4" - mkdirp "^0.5.0" - postcss "^7.0.2" - xxhashjs "^0.2.1" - -postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: - version "3.3.1" - resolved "https://registry.npm.taobao.org/postcss-value-parser/download/postcss-value-parser-3.3.1.tgz?cache=0&sync_timestamp=1582038149197&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-value-parser%2Fdownload%2Fpostcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha1-n/giVH4okyE88cMO+lGsX9G6goE= - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3: - version "4.0.3" - resolved "https://registry.npm.taobao.org/postcss-value-parser/download/postcss-value-parser-4.0.3.tgz?cache=0&sync_timestamp=1582038149197&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss-value-parser%2Fdownload%2Fpostcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" - integrity sha1-ZR/0WTqp7ajV0NZlk6JBeurrMl0= - -postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/postcss-values-parser/download/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" - integrity sha1-2otHLZAdoeIFtHvcmGN7np5VDl8= - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.27" - resolved "https://registry.npm.taobao.org/postcss/download/postcss-7.0.27.tgz?cache=0&sync_timestamp=1582022111849&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpostcss%2Fdownload%2Fpostcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha1-zGfNxrDao3UQW3xCSoVWc0X8VNk= - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -prebuild-install@^5.3.3: - version "5.3.3" - resolved "https://registry.npm.taobao.org/prebuild-install/download/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" - integrity sha1-70BSuqxg1GX1umvwA8nB3nm52o4= - dependencies: - detect-libc "^1.0.3" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.0" - mkdirp "^0.5.1" - napi-build-utils "^1.0.1" - node-abi "^2.7.0" - noop-logger "^0.1.1" - npmlog "^4.0.1" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^3.0.3" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - which-pm-runs "^1.0.0" - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.npm.taobao.org/prepend-http/download/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -prettier@^1.18.2: - version "1.19.1" - resolved "https://registry.npm.taobao.org/prettier/download/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha1-99f1/4qc2HKnvkyhQglZVqYHl8s= - -pretty-bytes@^5.3.0: - version "5.3.0" - resolved "https://registry.npm.taobao.org/pretty-bytes/download/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" - integrity sha1-8oSeJ9t5+01s/iR2T8QTTxZZifI= - -pretty-error@^2.0.2: - version "2.1.1" - resolved "https://registry.npm.taobao.org/pretty-error/download/pretty-error-2.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpretty-error%2Fdownload%2Fpretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= - dependencies: - renderkid "^2.0.1" - utila "~0.4" - -pretty-time@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/pretty-time/download/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" - integrity sha1-/7dCmvq7hTXDRqNOQYc63z103Q4= - -private@^0.1.8: - version "0.1.8" - resolved "https://registry.npm.taobao.org/private/download/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8= - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha1-eCDZsWEgzFXKmud5JoCufbptf+I= - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npm.taobao.org/process/download/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/promise-inflight/download/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -proper-lockfile@^4.1.1: - version "4.1.1" - resolved "https://registry.npm.taobao.org/proper-lockfile/download/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c" - integrity sha1-KEz5254wqQ5kevrWnet8sGiBJiw= - dependencies: - graceful-fs "^4.1.11" - retry "^0.12.0" - signal-exit "^3.0.2" - -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha1-/cIzZQVEfT8vLGOO0nLK9hS7sr8= - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.1" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/prr/download/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha1-kyb4vPsBOtzABf3/BWrM4CDlHCQ= - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.npm.taobao.org/public-encrypt/download/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha1-T8ydd6B+SLp1J+fL4N4z0HATMeA= - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/pump/download/pump-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpump%2Fdownload%2Fpump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk= - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/pump/download/pump-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpump%2Fdownload%2Fpump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ= - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.npm.taobao.org/pumpify/download/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4= - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha1-tYsBCsQMIsVldhbI0sLALHv0eew= - -q@^1.1.2: - version "1.5.1" - resolved "https://registry.npm.taobao.org/q/download/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz?cache=0&sync_timestamp=1585168573189&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqs%2Fdownload%2Fqs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha1-QdwaAV49WB8WIXdr4xr7KHapsbw= - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz?cache=0&sync_timestamp=1585168573189&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqs%2Fdownload%2Fqs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha1-yzroBuh0BERYTvFUzo7pjUA/PjY= - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.npm.taobao.org/query-string/download/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.npm.taobao.org/querystring-es3/download/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0, querystring@^0.2.0: - version "0.2.0" - resolved "https://registry.npm.taobao.org/querystring/download/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.1.0" - resolved "https://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frandombytes%2Fdownload%2Frandombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo= - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.npm.taobao.org/randomfill/download/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha1-ySGW/IarQr6YPxvzF3giSTHWFFg= - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE= - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz?cache=0&sync_timestamp=1561549041659&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fraw-body%2Fdownload%2Fraw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha1-oc5vucm8NWylLoklarWQWeE9AzI= - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -raw-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/raw-loader/download/raw-loader-4.0.0.tgz#d639c40fb9d72b5c7f8abc1fb2ddb25b29d3d540" - integrity sha1-1jnED7nXK1x/irwfst2yWynT1UA= - dependencies: - loader-utils "^1.2.3" - schema-utils "^2.5.0" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.npm.taobao.org/rc/download/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0= - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/read-cache/download/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= - dependencies: - pify "^2.3.0" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/read-pkg/download/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha1-M3u9o63AcGvT4CRCaihtS0sskZg= - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha1-DodiKjMlqjPokihcr4tOhGUppSU= - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha1-mERY0ToeQuLp9YQbEp4WLzaa/xc= - dependencies: - picomatch "^2.0.7" - -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/redent/download/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.npm.taobao.org/regenerate-unicode-properties/download/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha1-5d5xEdZV57pgwFfb6f83yH5lzew= - dependencies: - regenerate "^1.4.0" - -regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.npm.taobao.org/regenerate/download/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha1-SoVuxLVuQHfFV1icroXnpMiGmhE= - -regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha1-2Hih0JS0MG0QuQlkhLM+vVXiZpc= - -regenerator-transform@^0.14.2: - version "0.14.4" - resolved "https://registry.npm.taobao.org/regenerator-transform/download/regenerator-transform-0.14.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-transform%2Fdownload%2Fregenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" - integrity sha1-UmaFeJZRjRYWp4oEeTN6MOqXTMc= - dependencies: - "@babel/runtime" "^7.8.4" - private "^0.1.8" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/regex-not/download/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw= - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexpu-core@^4.7.0: - version "4.7.0" - resolved "https://registry.npm.taobao.org/regexpu-core/download/regexpu-core-4.7.0.tgz?cache=0&sync_timestamp=1583949899397&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregexpu-core%2Fdownload%2Fregexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" - integrity sha1-/L9FjFBDGwu3tF1pZ7gZLZHz2Tg= - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -regjsgen@^0.5.1: - version "0.5.1" - resolved "https://registry.npm.taobao.org/regjsgen/download/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" - integrity sha1-SPC/Gl6iBRlpKcDZeYtC0e2YRDw= - -regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.npm.taobao.org/regjsparser/download/regjsparser-0.6.4.tgz?cache=0&sync_timestamp=1583896992574&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregjsparser%2Fdownload%2Fregjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha1-p2n4aEMIQBpm6bUp0kNv9NBmYnI= - dependencies: - jsesc "~0.5.0" - -relateurl@0.2.x, relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.npm.taobao.org/relateurl/download/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.npm.taobao.org/remove-trailing-separator/download/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.npm.taobao.org/renderkid/download/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha1-OAF5wv9a4TZcUivy/Pz/AcW3QUk= - dependencies: - css-select "^1.1.0" - dom-converter "^0.2" - htmlparser2 "^3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.npm.taobao.org/repeat-element/download/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4= - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/repeating/download/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -request@^2.87.0, request@^2.88.0: - version "2.88.2" - resolved "https://registry.npm.taobao.org/request/download/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha1-1zyRhzHLWofaBH4gcjQUb2ZNErM= - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/require-directory/download/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/require-main-filename/download/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/requires-port/download/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/resolve-from/download/resolve-from-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve-from%2Fdownload%2Fresolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.npm.taobao.org/resolve-url/download/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.1.7, resolve@^1.10.0, resolve@^1.2.0, resolve@^1.3.2, resolve@^1.8.1: - version "1.15.1" - resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.15.1.tgz?cache=0&sync_timestamp=1580943346382&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha1-J73N7/6vLWJEuVuw+fS0ZTRR8+g= - dependencies: - path-parse "^1.0.6" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.npm.taobao.org/ret/download/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w= - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npm.taobao.org/retry/download/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -rewrite-imports@^2.0.3: - version "2.0.3" - resolved "https://registry.npm.taobao.org/rewrite-imports/download/rewrite-imports-2.0.3.tgz#210fc05ebda6a6c6a2e396608b0146003d510dda" - integrity sha1-IQ/AXr2mpsai45ZgiwFGAD1RDdo= - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/rgb-regex/download/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/rgba-regex/download/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - -rimraf@2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: - version "2.7.1" - resolved "https://registry.npm.taobao.org/rimraf/download/rimraf-2.7.1.tgz?cache=0&sync_timestamp=1581230370030&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frimraf%2Fdownload%2Frimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w= - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.npm.taobao.org/ripemd160/download/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha1-ocGm9iR1FXe6XQeRTLyShQWFiQw= - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.npm.taobao.org/run-queue/download/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= - dependencies: - aproba "^1.1.1" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz?cache=0&sync_timestamp=1562350533022&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.0.tgz?cache=0&sync_timestamp=1562350533022&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha1-t02uxJsRSPiMZLaNSbHoFcHy9Rk= - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= - -sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.npm.taobao.org/sass-graph/download/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= - dependencies: - glob "^7.0.0" - lodash "^4.0.0" - scss-tokenizer "^0.2.3" - yargs "^7.0.0" - -sass-loader@^8.0.2: - version "8.0.2" - resolved "https://registry.npm.taobao.org/sass-loader/download/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" - integrity sha1-3r7NjDziQ8dkVPLoKQSCFQOACQ0= - dependencies: - clone-deep "^4.0.1" - loader-utils "^1.2.3" - neo-async "^2.6.1" - schema-utils "^2.6.1" - semver "^6.3.0" - -sax@~1.2.4: - version "1.2.4" - resolved "https://registry.npm.taobao.org/sax/download/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha1-KBYjTiN4vdxOU1T6tcqold9xANk= - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/schema-utils/download/schema-utils-1.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A= - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.5: - version "2.6.5" - resolved "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.6.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-2.6.5.tgz#c758f0a7e624263073d396e29cd40aa101152d8a" - integrity sha1-x1jwp+YkJjBz05binNQKoQEVLYo= - dependencies: - ajv "^6.12.0" - ajv-keywords "^3.4.1" - -scss-tokenizer@^0.2.3: - version "0.2.3" - resolved "https://registry.npm.taobao.org/scss-tokenizer/download/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" - integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= - dependencies: - js-base64 "^2.1.8" - source-map "^0.4.2" - -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha1-qVT5Ma66UI0we78Gnv8MAclhFvc= - -semver@7.0.0: - version "7.0.0" - resolved "https://registry.npm.taobao.org/semver/download/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha1-XzyjV2HkfgWyBsba/yz4FPAxa44= - -semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0= - -semver@^7.1.3: - version "7.2.2" - resolved "https://registry.npm.taobao.org/semver/download/semver-7.2.2.tgz#d01432d74ed3010a20ffaf909d63a691520521cd" - integrity sha1-0BQy107TAQog/6+QnWOmkVIFIc0= - -semver@~5.3.0: - version "5.3.0" - resolved "https://registry.npm.taobao.org/semver/download/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= - -send@0.17.1: - version "0.17.1" - resolved "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg= - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-2.1.2.tgz?cache=0&sync_timestamp=1581860611899&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fserialize-javascript%2Fdownload%2Fserialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha1-7OxTsOAxe9yV73arcHS3OEeF+mE= - -serialize-javascript@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-3.0.0.tgz?cache=0&sync_timestamp=1581860611899&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fserialize-javascript%2Fdownload%2Fserialize-javascript-3.0.0.tgz#492e489a2d77b7b804ad391a5f5d97870952548e" - integrity sha1-SS5Imi13t7gErTkaX12XhwlSVI4= - -serve-placeholder@^1.2.2: - version "1.2.2" - resolved "https://registry.npm.taobao.org/serve-placeholder/download/serve-placeholder-1.2.2.tgz#034960945b5950f873b2be4e4ea3a4653b9e33e5" - integrity sha1-A0lglFtZUPhzsr5OTqOkZTueM+U= - dependencies: - defu "^1.0.0" - -serve-static@1.14.1, serve-static@^1.14.1: - version "1.14.1" - resolved "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk= - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -server-destroy@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/server-destroy/download/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" - integrity sha1-8Tv5KOQrnD55OD5hzDmYtdFObN0= - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/set-value/download/set-value-2.0.1.tgz?cache=0&sync_timestamp=1585774774019&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fset-value%2Fdownload%2Fset-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha1-oY1AUw5vB95CKMfe/kInr4ytAFs= - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.npm.taobao.org/setimmediate/download/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM= - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.npm.taobao.org/sha.js/download/sha.js-2.4.11.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsha.js%2Fdownload%2Fsha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha1-N6XPC4HsvGlD3hCbopYNGyZYSuc= - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.npm.taobao.org/shallow-clone/download/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha1-jymBrZJTH1UDWwH7IwdppA4C76M= - dependencies: - kind-of "^6.0.2" - -sharp@^0.24.0: - version "0.24.1" - resolved "https://registry.npm.taobao.org/sharp/download/sharp-0.24.1.tgz#f853f9f495dfde05d452179b4f9f31dfc400f4ca" - integrity sha1-+FP59JXf3gXUUhebT58x38QA9Mo= - dependencies: - color "^3.1.2" - detect-libc "^1.0.3" - nan "^2.14.0" - npmlog "^4.1.2" - prebuild-install "^5.3.3" - semver "^7.1.3" - simple-get "^3.1.0" - tar "^6.0.1" - tunnel-agent "^0.6.0" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/shebang-command/download/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha1-zNCvT4g1+9wmW4JGGq8MNmY/NOo= - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/shebang-regex/download/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/shebang-regex/download/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha1-rhbxZE2HPsrYQ7AwexQzYtTEIXI= - -shell-quote@^1.6.1: - version "1.7.2" - resolved "https://registry.npm.taobao.org/shell-quote/download/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" - integrity sha1-Z6fQLHbJ2iT5nSCAj8re0ODgS+I= - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.3.tgz?cache=0&sync_timestamp=1585253323149&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsignal-exit%2Fdownload%2Fsignal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha1-oUEMLt2PB3sItOJTyOrPyvBXRhw= - -simple-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/simple-concat/download/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" - integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= - -simple-get@^3.0.3, simple-get@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/simple-get/download/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha1-tFvgYkNeUNFZVAtXYgLO7EC5xrM= - dependencies: - decompress-response "^4.2.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npm.taobao.org/simple-swizzle/download/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.npm.taobao.org/snapdragon-node/download/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha1-bBdfhv8UvbByRWPo88GwIaKGhTs= - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.npm.taobao.org/snapdragon-util/download/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI= - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.npm.taobao.org/snapdragon/download/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0= - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.npm.taobao.org/sort-keys/download/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" - -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/sort-keys/download/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/source-list-map/download/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha1-OZO9hzv8SEecyp6jpUeDXHwVSzQ= - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.3.tgz?cache=0&sync_timestamp=1584830247375&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsource-map-resolve%2Fdownload%2Fsource-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha1-GQhmvs51U+H48mei7oLGBrVQmho= - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.npm.taobao.org/source-map-support/download/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha1-CuBp5/47p1OMZMmFFeNTOerFoEI= - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.npm.taobao.org/source-map-url/download/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@0.5.6: - version "0.5.6" - resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= - -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.0, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM= - -spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - integrity sha1-+4PlBERSaPFUsHTiGMh8ADzTHfQ= - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/spdx-exceptions/download/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha1-LqRQrudPKom/uUUZwH/Nb0EyKXc= - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.npm.taobao.org/spdx-expression-parse/download/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha1-meEZt6XaAOBUkcn6M4t5BII7QdA= - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.npm.taobao.org/spdx-license-ids/download/spdx-license-ids-3.0.5.tgz?cache=0&sync_timestamp=1562837472724&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fspdx-license-ids%2Fdownload%2Fspdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha1-NpS1gEVnpFjTyARYQqY1hjL2JlQ= - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.npm.taobao.org/split-string/download/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha1-fLCd2jqGWFcFxks5pkZgOGguj+I= - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.npm.taobao.org/sshpk/download/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha1-+2YcC+8ps520B2nuOfpwCT1vaHc= - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.npm.taobao.org/ssri/download/ssri-6.0.1.tgz?cache=0&sync_timestamp=1581989445149&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fssri%2Fdownload%2Fssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha1-KjxBso3UW2K2Nnbst0ABJlrp7dg= - dependencies: - figgy-pudding "^3.5.1" - -ssri@^7.0.0: - version "7.1.0" - resolved "https://registry.npm.taobao.org/ssri/download/ssri-7.1.0.tgz?cache=0&sync_timestamp=1581989445149&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fssri%2Fdownload%2Fssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" - integrity sha1-ksJBv23oI2W1x/tL126XVSLhKU0= - dependencies: - figgy-pudding "^3.5.1" - minipass "^3.1.1" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.npm.taobao.org/stable/download/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88= - -stack-trace@0.0.10: - version "0.0.10" - resolved "https://registry.npm.taobao.org/stack-trace/download/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= - -stackframe@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/stackframe/download/stackframe-1.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstackframe%2Fdownload%2Fstackframe-1.1.1.tgz#ffef0a3318b1b60c3b58564989aca5660729ec71" - integrity sha1-/+8KMxixtgw7WFZJiaylZgcp7HE= - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.npm.taobao.org/static-extend/download/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -std-env@^2.2.1: - version "2.2.1" - resolved "https://registry.npm.taobao.org/std-env/download/std-env-2.2.1.tgz#2ffa0fdc9e2263e0004c1211966e960948a40f6b" - integrity sha1-L/oP3J4iY+AATBIRlm6WCUikD2s= - dependencies: - ci-info "^1.6.0" - -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.npm.taobao.org/stdout-stream/download/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha1-WsF0zdXNcmEEqgwLK9g4FdjVNd4= - dependencies: - readable-stream "^2.0.1" - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.npm.taobao.org/stream-browserify/download/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha1-h1IdOKRKp+6RzhzSpH3wy0ndZgs= - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.npm.taobao.org/stream-each/download/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha1-6+J6DDibBPvMIzZClS4Qcxr6m64= - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.npm.taobao.org/stream-http/download/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha1-stJCRpKIpaJ+xP6JM6z2I95lFPw= - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/stream-shift/download/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha1-1wiCgVWasneEJCebCHfaPDktWj0= - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.npm.taobao.org/strict-uri-encode/download/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0: - version "2.1.1" - resolved "https://registry.npm.taobao.org/string-width/download/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4= - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/string-width/download/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha1-InZ74htirxCBV0MG9prFG2IgOWE= - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0: - version "4.2.0" - resolved "https://registry.npm.taobao.org/string-width/download/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha1-lSGCxGzHssMT0VluYjmSvRY7crU= - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/string.prototype.trimend/download/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha1-hYEqa4R6wAInD1gIFGBkyZX7aRM= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.npm.taobao.org/string.prototype.trimleft/download/string.prototype.trimleft-2.1.2.tgz?cache=0&sync_timestamp=1585557209387&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring.prototype.trimleft%2Fdownload%2Fstring.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha1-RAiqLl1t3QyagHObCH+8BnwDs8w= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.npm.taobao.org/string.prototype.trimright/download/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha1-x28c7zDyG7rYr+uNsVEUls+w8qM= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/string.prototype.trimstart/download/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha1-FK9tnzSwU/fPyJty+PLuFLkDmlQ= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha1-nPFhG6YmhdcDCunkujQUnDrwP8g= - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.1.0: - version "5.2.0" - resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4= - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha1-CxVx3XZpzNTz4G4U7x7tJiJa5TI= - dependencies: - ansi-regex "^5.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/strip-bom/download/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/strip-eof/download/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/strip-final-newline/download/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha1-ibhS+y/L6Tb29LMYevsKEsGrWK0= - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/strip-indent/download/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -style-resources-loader@^1.3.3: - version "1.3.3" - resolved "https://registry.npm.taobao.org/style-resources-loader/download/style-resources-loader-1.3.3.tgz#e4b3ab93e7c3d1606e86f9549522a0b5c4ad6812" - integrity sha1-5LOrk+fD0WBuhvlUlSKgtcStaBI= - dependencies: - glob "^7.1.6" - is-promise "^2.1.0" - loader-utils "^1.2.3" - schema-utils "^2.6.1" - -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.npm.taobao.org/stylehacks/download/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha1-Zxj8r00eB9ihMYaQiB6NlnJqcdU= - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz?cache=0&sync_timestamp=1573220230429&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz?cache=0&sync_timestamp=1573220230429&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha1-4uaaRKyHcveKHsCzW2id9lMO/I8= - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz?cache=0&sync_timestamp=1573220230429&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha1-B2Srxpxj1ayELdSGfo0CXogN+PM= - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-7.1.0.tgz?cache=0&sync_timestamp=1573220230429&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha1-aOMlkd9z4lrRxLSRCKLsUHliv9E= - dependencies: - has-flag "^4.0.0" - -svg-tags@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/svg-tags/download/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" - integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= - -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.npm.taobao.org/svgo/download/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha1-ttxRHAYzRsnkFbgeQ0ARRbltQWc= - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -tapable@^1.0.0, tapable@^1.0.0-beta.5, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha1-ofzMBrWNth/XpF2i2kT186Pme6I= - -tar-fs@^2.0.0: - version "2.0.1" - resolved "https://registry.npm.taobao.org/tar-fs/download/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" - integrity sha1-5ECGwcYNMaTwz4k7HE4VXav66eI= - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.0.0" - -tar-stream@^2.0.0: - version "2.1.2" - resolved "https://registry.npm.taobao.org/tar-stream/download/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" - integrity sha1-bV7xp+V4OpX/cLabl0VaWWjcEyU= - dependencies: - bl "^4.0.1" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@^2.0.0: - version "2.2.2" - resolved "https://registry.npm.taobao.org/tar/download/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha1-DKiEhWLHKZuLRG/2pNYM27I+3EA= - dependencies: - block-stream "*" - fstream "^1.0.12" - inherits "2" - -tar@^6.0.1: - version "6.0.1" - resolved "https://registry.npm.taobao.org/tar/download/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa" - integrity sha1-ezvWwxPLbgFTdwEI+NcKwphgfvo= - dependencies: - chownr "^1.1.3" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.0" - mkdirp "^1.0.3" - yallist "^4.0.0" - -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.npm.taobao.org/term-size/download/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha1-Hxat7f6b3BiADhd2ghc0CG/MZ1M= - -terser-webpack-plugin@^1.4.3: - version "1.4.3" - resolved "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-1.4.3.tgz?cache=0&sync_timestamp=1581697973446&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" - integrity sha1-Xsry29xfuZdF/QZ5H0b8ndscmnw= - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser-webpack-plugin@^2.3.5: - version "2.3.5" - resolved "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-2.3.5.tgz?cache=0&sync_timestamp=1581697973446&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-2.3.5.tgz#5ad971acce5c517440ba873ea4f09687de2f4a81" - integrity sha1-WtlxrM5cUXRAuoc+pPCWh94vSoE= - dependencies: - cacache "^13.0.1" - find-cache-dir "^3.2.0" - jest-worker "^25.1.0" - p-limit "^2.2.2" - schema-utils "^2.6.4" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.4.3" - webpack-sources "^1.4.3" - -terser@^4.1.2, terser@^4.4.3: - version "4.6.11" - resolved "https://registry.npm.taobao.org/terser/download/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f" - integrity sha1-Ev+Z/dYqJt4qgvUIUVQH62zNip8= - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.npm.taobao.org/text-table/download/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -thread-loader@^2.1.3: - version "2.1.3" - resolved "https://registry.npm.taobao.org/thread-loader/download/thread-loader-2.1.3.tgz#cbd2c139fc2b2de6e9d28f62286ab770c1acbdda" - integrity sha1-y9LBOfwrLebp0o9iKGq3cMGsvdo= - dependencies: - loader-runner "^2.3.1" - loader-utils "^1.1.0" - neo-async "^2.6.0" - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.npm.taobao.org/through2/download/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0= - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -time-fix-plugin@^2.0.6: - version "2.0.6" - resolved "https://registry.npm.taobao.org/time-fix-plugin/download/time-fix-plugin-2.0.6.tgz#3210121d269b475a7e7661766e682bd768ba1ced" - integrity sha1-MhASHSabR1p+dmF2bmgr12i6HO0= - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.npm.taobao.org/timers-browserify/download/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha1-gAsfPu4nLlvFPuRloE0OgEwxIR8= - dependencies: - setimmediate "^1.0.4" - -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.npm.taobao.org/timsort/download/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-2.0.0.tgz?cache=0&sync_timestamp=1580550651593&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fto-fast-properties%2Fdownload%2Fto-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.npm.taobao.org/to-object-path/download/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ= - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.npm.taobao.org/to-regex/download/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4= - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM= - -toposort@^1.0.0: - version "1.0.7" - resolved "https://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" - integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha1-zZ+yoKodWhK0c72fuW+j3P9lreI= - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/trim-newlines/download/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - -"true-case-path@^1.0.2": - version "1.0.3" - resolved "https://registry.npm.taobao.org/true-case-path/download/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" - integrity sha1-+BO1qMhrQNpZYGcisUTjIleZ9H0= - dependencies: - glob "^7.1.2" - -tryer@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/tryer/download/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" - integrity sha1-8shUBoALmw90yfdGW4HqrSQSUvg= - -tslib@^1.9.0: - version "1.11.1" - resolved "https://registry.npm.taobao.org/tslib/download/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha1-6xXRKIJ/vuKEFUnhcfRe0zisfjU= - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.npm.taobao.org/tty-browserify/download/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.npm.taobao.org/tunnel-agent/download/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.npm.taobao.org/tweetnacl/download/tweetnacl-0.14.5.tgz?cache=0&sync_timestamp=1581364267007&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftweetnacl%2Fdownload%2Ftweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.npm.taobao.org/type-fest/download/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha1-l6vwhyMQ/tiKXEZrJWgVdhReM/E= - -type-fest@^0.8.0, type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.npm.taobao.org/type-fest/download/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha1-CeJJ696FHTseSNJ8EFREZn8XuD0= - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha1-TlUs0F3wlGfcvE73Od6J8s83wTE= - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -ua-parser-js@^0.7.21: - version "0.7.21" - resolved "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.21.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fua-parser-js%2Fdownload%2Fua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha1-hTz5zpP2QvZxdCc8w0Vlrm8wh3c= - -uglify-js@3.4.x: - version "3.4.10" - resolved "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.4.10.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuglify-js%2Fdownload%2Fuglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" - integrity sha1-mtlWPY6zrN+404WX0q8dgV9qdV8= - dependencies: - commander "~2.19.0" - source-map "~0.6.1" - -uglify-js@^3.5.1: - version "3.8.1" - resolved "https://registry.npm.taobao.org/uglify-js/download/uglify-js-3.8.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuglify-js%2Fdownload%2Fuglify-js-3.8.1.tgz#43bb15ce6f545eaa0a64c49fd29375ea09fa0f93" - integrity sha1-Q7sVzm9UXqoKZMSf0pN16gn6D5M= - dependencies: - commander "~2.20.3" - source-map "~0.6.1" - -unfetch@^4.1.0: - version "4.1.0" - resolved "https://registry.npm.taobao.org/unfetch/download/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db" - integrity sha1-bsLdDeiH5YpN7oOgUN7YD/xBN9s= - -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.npm.taobao.org/unicode-canonical-property-names-ecmascript/download/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - integrity sha1-JhmADEyCWADv3YNDr33Zkzy+KBg= - -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.npm.taobao.org/unicode-match-property-ecmascript/download/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - integrity sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw= - dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" - -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/unicode-match-property-value-ecmascript/download/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha1-DZH2AO7rMJaqlisdb8iIduZOpTE= - -unicode-property-aliases-ecmascript@^1.0.4: - version "1.1.0" - resolved "https://registry.npm.taobao.org/unicode-property-aliases-ecmascript/download/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" - integrity sha1-3Vepn2IHvt/0Yoq++5TFDblByPQ= - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/union-value/download/union-value-1.0.1.tgz?cache=0&sync_timestamp=1561410740419&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funion-value%2Fdownload%2Funion-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc= - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/uniq/download/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/uniqs/download/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/unique-filename/download/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha1-HWl2k2mtoFgxA6HmrodoG1ZXMjA= - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.npm.taobao.org/unique-slug/download/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha1-uqvOkQg/xk6UWw861hPiZPfNTmw= - dependencies: - imurmurhash "^0.1.4" - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY= - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/unquote/download/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/unset-value/download/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1, upath@^1.2.0: - version "1.2.0" - resolved "https://registry.npm.taobao.org/upath/download/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha1-j2bbzVWog6za5ECK+LA1pQRMGJQ= - -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.npm.taobao.org/upper-case/download/upper-case-1.1.3.tgz?cache=0&sync_timestamp=1575601728945&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fupper-case%2Fdownload%2Fupper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.npm.taobao.org/uri-js/download/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha1-lMVA4f93KVbiKZUHwBCupsiDjrA= - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.npm.taobao.org/urix/download/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-loader@^2.3.0: - version "2.3.0" - resolved "https://registry.npm.taobao.org/url-loader/download/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b" - integrity sha1-4OLvZY8APvuMpBsPP/v3a6uIZYs= - dependencies: - loader-utils "^1.2.3" - mime "^2.4.4" - schema-utils "^2.5.0" - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.npm.taobao.org/url/download/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.npm.taobao.org/use/download/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8= - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util.promisify@1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/util.promisify/download/util.promisify-1.0.0.tgz?cache=0&sync_timestamp=1579206947343&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Futil.promisify%2Fdownload%2Futil.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA= - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.npm.taobao.org/util.promisify/download/util.promisify-1.0.1.tgz?cache=0&sync_timestamp=1579206947343&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Futil.promisify%2Fdownload%2Futil.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha1-a693dLgO6w91INi4HQeYKlmruu4= - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.npm.taobao.org/util/download/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.npm.taobao.org/util/download/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha1-MjZzNyDsZLsn9uJvQhqqLhtYjWE= - dependencies: - inherits "2.0.3" - -util@^0.12.0: - version "0.12.2" - resolved "https://registry.npm.taobao.org/util/download/util-0.12.2.tgz#54adb634c9e7c748707af2bf5a8c7ab640cbba2b" - integrity sha1-VK22NMnnx0hwevK/Wox6tkDLuis= - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - safe-buffer "^5.1.2" - -utila@^0.4.0, utila@~0.4: - version "0.4.0" - resolved "https://registry.npm.taobao.org/utila/download/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.4.0.tgz?cache=0&sync_timestamp=1585683808452&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4= - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha1-/JH2uce6FchX9MssXe/uw51PQQo= - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@^1.1.2, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.npm.taobao.org/vendors/download/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha1-4rgApT56Kbk1BsPPQRANFsTErY4= - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.npm.taobao.org/verror/download/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA= - -vue-client-only@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/vue-client-only/download/vue-client-only-2.0.0.tgz#ddad8d675ee02c761a14229f0e440e219de1da1c" - integrity sha1-3a2NZ17gLHYaFCKfDkQOIZ3h2hw= - -vue-highlightjs@^1.3.3: - version "1.3.3" - resolved "https://registry.npm.taobao.org/vue-highlightjs/download/vue-highlightjs-1.3.3.tgz#29a0d57132fc1ce15cfa61e896918f5b718c5d52" - integrity sha1-KaDVcTL8HOFc+mHolpGPW3GMXVI= - dependencies: - highlight.js "*" - -vue-hot-reload-api@^2.3.0: - version "2.3.4" - resolved "https://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" - integrity sha1-UylVzB6yCKPZkLOp+acFdGV+CPI= - -vue-loader@^15.9.1: - version "15.9.1" - resolved "https://registry.npm.taobao.org/vue-loader/download/vue-loader-15.9.1.tgz#bd2ab8f3d281e51d7b81d15390a58424d142243e" - integrity sha1-vSq489KB5R17gdFTkKWEJNFCJD4= - dependencies: - "@vue/component-compiler-utils" "^3.1.0" - hash-sum "^1.0.2" - loader-utils "^1.1.0" - vue-hot-reload-api "^2.3.0" - vue-style-loader "^4.1.0" - -vue-meta@^2.3.3: - version "2.3.3" - resolved "https://registry.npm.taobao.org/vue-meta/download/vue-meta-2.3.3.tgz#2a097f62817204b0da78be4d62aee0cb566eaee0" - integrity sha1-Kgl/YoFyBLDaeL5NYq7gy1ZuruA= - dependencies: - deepmerge "^4.2.2" - -vue-no-ssr@^1.1.1: - version "1.1.1" - resolved "https://registry.npm.taobao.org/vue-no-ssr/download/vue-no-ssr-1.1.1.tgz#875f3be6fb0ae41568a837f3ac1a80eaa137b998" - integrity sha1-h1875vsK5BVoqDfzrBqA6qE3uZg= - -vue-router@^3.1.6: - version "3.1.6" - resolved "https://registry.npm.taobao.org/vue-router/download/vue-router-3.1.6.tgz?cache=0&sync_timestamp=1586343255852&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-3.1.6.tgz#45f5a3a3843e31702c061dd829393554e4328f89" - integrity sha1-RfWjo4Q+MXAsBh3YKTk1VOQyj4k= - -vue-server-renderer@^2.6.11: - version "2.6.11" - resolved "https://registry.npm.taobao.org/vue-server-renderer/download/vue-server-renderer-2.6.11.tgz#be8c9abc6aacc309828a755c021a05fc474b4bc3" - integrity sha1-voyavGqswwmCinVcAhoF/EdLS8M= - dependencies: - chalk "^1.1.3" - hash-sum "^1.0.2" - he "^1.1.0" - lodash.template "^4.5.0" - lodash.uniq "^4.5.0" - resolve "^1.2.0" - serialize-javascript "^2.1.2" - source-map "0.5.6" - -vue-style-loader@^4.1.0: - version "4.1.2" - resolved "https://registry.npm.taobao.org/vue-style-loader/download/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8" - integrity sha1-3t80mAbyXOtOZPOtfApE+6c1/Pg= - dependencies: - hash-sum "^1.0.2" - loader-utils "^1.0.2" - -vue-template-compiler@^2.6.11: - version "2.6.11" - resolved "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080" - integrity sha1-wEcE749JixUxMAGJk+VjCdRpgIA= - dependencies: - de-indent "^1.0.2" - he "^1.1.0" - -vue-template-es2015-compiler@^1.9.0: - version "1.9.1" - resolved "https://registry.npm.taobao.org/vue-template-es2015-compiler/download/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" - integrity sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU= - -vue@^2.6.11: - version "2.6.11" - resolved "https://registry.npm.taobao.org/vue/download/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5" - integrity sha1-dllNh31LEiNEBuhONSdcbVFBJcU= - -vuex@^3.1.3: - version "3.1.3" - resolved "https://registry.npm.taobao.org/vuex/download/vuex-3.1.3.tgz#f2ad73e3fb73691698b38c93f66e58e267947180" - integrity sha1-8q1z4/tzaRaYs4yT9m5Y4meUcYA= - -watchpack@^1.6.0: - version "1.6.1" - resolved "https://registry.npm.taobao.org/watchpack/download/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" - integrity sha1-KA2gqHGFkhdAEMB4x1hadM2M0OI= - dependencies: - chokidar "^2.1.8" - graceful-fs "^4.1.2" - neo-async "^2.5.0" - -webpack-bundle-analyzer@^3.6.1: - version "3.6.1" - resolved "https://registry.npm.taobao.org/webpack-bundle-analyzer/download/webpack-bundle-analyzer-3.6.1.tgz#bdb637c2304424f2fbff9a950c7be42a839ae73b" - integrity sha1-vbY3wjBEJPL7/5qVDHvkKoOa5zs= - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - bfj "^6.1.1" - chalk "^2.4.1" - commander "^2.18.0" - ejs "^2.6.1" - express "^4.16.3" - filesize "^3.6.1" - gzip-size "^5.0.0" - lodash "^4.17.15" - mkdirp "^0.5.1" - opener "^1.5.1" - ws "^6.0.0" - -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.npm.taobao.org/webpack-dev-middleware/download/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha1-ABnD23FuP6XOy/ZPKriKdLqzMfM= - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-external-import@^1.1.0-beta.3: - version "1.1.3" - resolved "https://registry.npm.taobao.org/webpack-external-import/download/webpack-external-import-1.1.3.tgz#9782c1df397f5847c02be6507eadbf16be9991b6" - integrity sha1-l4LB3zl/WEfAK+ZQfq2/Fr6ZkbY= - dependencies: - assert "^2.0.0" - dimport "^1.0.0" - fs-extra "^8.1.0" - loadjs "^4.2.0" - pkg-up "^3.1.0" - -webpack-hot-middleware@^2.25.0: - version "2.25.0" - resolved "https://registry.npm.taobao.org/webpack-hot-middleware/download/webpack-hot-middleware-2.25.0.tgz#4528a0a63ec37f8f8ef565cf9e534d57d09fe706" - integrity sha1-RSigpj7Df4+O9WXPnlNNV9Cf5wY= - dependencies: - ansi-html "0.0.7" - html-entities "^1.2.0" - querystring "^0.2.0" - strip-ansi "^3.0.0" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.npm.taobao.org/webpack-log/download/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha1-W3ko4GN1k/EZ0y9iJ8HgrDHhtH8= - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" - -webpack-node-externals@^1.7.2: - version "1.7.2" - resolved "https://registry.npm.taobao.org/webpack-node-externals/download/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3" - integrity sha1-bh7nmsZ8BwQCunAO8DOpuNUqxOM= - -webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.npm.taobao.org/webpack-sources/download/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha1-7t2OwLko+/HL/plOItLYkPMwqTM= - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^4.42.1: - version "4.42.1" - resolved "https://registry.npm.taobao.org/webpack/download/webpack-4.42.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack%2Fdownload%2Fwebpack-4.42.1.tgz#ae707baf091f5ca3ef9c38b884287cfe8f1983ef" - integrity sha1-rnB7rwkfXKPvnDi4hCh8/o8Zg+8= - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.2.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.6.0" - webpack-sources "^1.4.1" - -webpackbar@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/webpackbar/download/webpackbar-4.0.0.tgz#ee7a87f16077505b5720551af413c8ecd5b1f780" - integrity sha1-7nqH8WB3UFtXIFUa9BPI7NWx94A= - dependencies: - ansi-escapes "^4.2.1" - chalk "^2.4.2" - consola "^2.10.0" - figures "^3.0.0" - pretty-time "^1.1.0" - std-env "^2.2.1" - text-table "^0.2.0" - wrap-ansi "^6.0.0" - -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/which-module/download/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= - -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.npm.taobao.org/which-pm-runs/download/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - -which@1, which@^1.2.9: - version "1.3.1" - resolved "https://registry.npm.taobao.org/which/download/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo= - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npm.taobao.org/which/download/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha1-fGqN0KY2oDJ+ELWckobu6T8/UbE= - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.npm.taobao.org/wide-align/download/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha1-rgdOa9wMFKQx6ATmJFScYzsABFc= - dependencies: - string-width "^1.0.2 || 2" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.npm.taobao.org/widest-line/download/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha1-gpIzO79my0X/DeFgOxNreuFJbso= - dependencies: - string-width "^4.0.0" - -workbox-cdn@^4.3.1: - version "4.3.1" - resolved "https://registry.npm.taobao.org/workbox-cdn/download/workbox-cdn-4.3.1.tgz#f1ffed5368c20291048498ba0744baf27dbd7294" - integrity sha1-8f/tU2jCApEEhJi6B0S68n29cpQ= - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.npm.taobao.org/worker-farm/download/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha1-JqlMU5G7ypJhUgAvabhKS/dy5ag= - dependencies: - errno "~0.1.7" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrap-ansi@^6.0.0, wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha1-6Tk7oHEC5skaOyIUePAlfNKFblM= - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^2.0.0: - version "2.4.3" - resolved "https://registry.npm.taobao.org/write-file-atomic/download/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha1-H9Lprh3z51uNjDZ0Q8aS1MqB9IE= - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - -write-json-file@^2.3.0: - version "2.3.0" - resolved "https://registry.npm.taobao.org/write-json-file/download/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" - integrity sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8= - dependencies: - detect-indent "^5.0.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - pify "^3.0.0" - sort-keys "^2.0.0" - write-file-atomic "^2.0.0" - -ws@^6.0.0: - version "6.2.1" - resolved "https://registry.npm.taobao.org/ws/download/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha1-RC/fCkftZPWbal2P8TD0dI7VJPs= - dependencies: - async-limiter "~1.0.0" - -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.npm.taobao.org/xtend/download/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q= - -xxhashjs@^0.2.1: - version "0.2.2" - resolved "https://registry.npm.taobao.org/xxhashjs/download/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" - integrity sha1-imJRVnYhocRqWuIE2gJJx/jKqdg= - dependencies: - cuint "^0.2.2" - -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.npm.taobao.org/y18n/download/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= - -y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/y18n/download/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha1-le+U+F7MgdAHwmThkKEg8KPIVms= - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.npm.taobao.org/yallist/download/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha1-27fa+b/YusmrRev2ArjLrQ1dCP0= - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npm.taobao.org/yallist/download/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI= - -yargs-parser@^5.0.0: - version "5.0.0" - resolved "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-5.0.0.tgz?cache=0&sync_timestamp=1585243716318&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs-parser%2Fdownload%2Fyargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" - integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= - dependencies: - camelcase "^3.0.0" - -yargs@^7.0.0: - version "7.1.0" - resolved "https://registry.npm.taobao.org/yargs/download/yargs-7.1.0.tgz?cache=0&sync_timestamp=1584344069946&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" - integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^5.0.0"