Merge inbound to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Sat, 05 May 2018 12:47:28 +0300
changeset 473150 ba62c1380491f06dd5b37365e4801b9a32ae2f0a
parent 473119 51d020e4c2cbf4de620648d7dcded9d78b285ace (current diff)
parent 473149 c1493d2344e9b085961d9d1684b250d710875d1c (diff)
child 473156 211e0bb05121b161a6eb6cd5acb0c9571ec0b79e
child 473172 8534948d69d4703af5e97e91022b15d4c30c17e8
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
ba62c1380491 / 61.0a1 / 20180505100434 / files
nightly linux64
ba62c1380491 / 61.0a1 / 20180505100434 / files
nightly mac
ba62c1380491 / 61.0a1 / 20180505100434 / files
nightly win32
ba62c1380491 / 61.0a1 / 20180505100434 / files
nightly win64
ba62c1380491 / 61.0a1 / 20180505100434 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/accessible/base/AccIterator.cpp
+++ b/accessible/base/AccIterator.cpp
@@ -13,17 +13,17 @@
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccIterator
 ////////////////////////////////////////////////////////////////////////////////
 
-AccIterator::AccIterator(Accessible* aAccessible,
+AccIterator::AccIterator(const Accessible* aAccessible,
                          filters::FilterFuncPtr aFilterFunc) :
   mFilterFunc(aFilterFunc)
 {
   mState = new IteratorState(aAccessible);
 }
 
 AccIterator::~AccIterator()
 {
@@ -58,17 +58,17 @@ AccIterator::Next()
   }
 
   return nullptr;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccIterator::IteratorState
 
-AccIterator::IteratorState::IteratorState(Accessible* aParent,
+AccIterator::IteratorState::IteratorState(const Accessible* aParent,
                                           IteratorState *mParentState) :
   mParent(aParent), mIndex(0), mParentState(mParentState)
 {
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // RelatedAccIterator
--- a/accessible/base/AccIterator.h
+++ b/accessible/base/AccIterator.h
@@ -33,35 +33,35 @@ private:
 
 /**
  * Allows to iterate through accessible children or subtree complying with
  * filter function.
  */
 class AccIterator : public AccIterable
 {
 public:
-  AccIterator(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
+  AccIterator(const Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
   virtual ~AccIterator();
 
   /**
    * Return next accessible complying with filter function. Return the first
    * accessible for the first time.
    */
   virtual Accessible* Next() override;
 
 private:
   AccIterator();
   AccIterator(const AccIterator&);
   AccIterator& operator =(const AccIterator&);
 
   struct IteratorState
   {
-    explicit IteratorState(Accessible* aParent, IteratorState* mParentState = nullptr);
+    explicit IteratorState(const Accessible* aParent, IteratorState* mParentState = nullptr);
 
-    Accessible* mParent;
+    const Accessible* mParent;
     int32_t mIndex;
     IteratorState* mParentState;
   };
 
   filters::FilterFuncPtr mFilterFunc;
   IteratorState* mState;
 };
 
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -439,19 +439,19 @@ public:
 
   /**
    * Return first/last/next/previous sibling of the accessible.
    */
   inline Accessible* NextSibling() const
     {  return GetSiblingAtOffset(1); }
   inline Accessible* PrevSibling() const
     { return GetSiblingAtOffset(-1); }
-  inline Accessible* FirstChild()
+  inline Accessible* FirstChild() const
     { return GetChildAt(0); }
-  inline Accessible* LastChild()
+  inline Accessible* LastChild() const
   {
     uint32_t childCount = ChildCount();
     return childCount != 0 ? GetChildAt(childCount - 1) : nullptr;
   }
 
   /**
    * Return embedded accessible children count.
    */
--- a/accessible/xul/XULTreeAccessible.cpp
+++ b/accessible/xul/XULTreeAccessible.cpp
@@ -1018,17 +1018,17 @@ XULTreeItemAccessibleBase::GetSiblingAtO
 
   return mParent->GetChildAt(IndexInParent() + aOffset);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeItemAccessibleBase: protected implementation
 
 bool
-XULTreeItemAccessibleBase::IsExpandable()
+XULTreeItemAccessibleBase::IsExpandable() const
 {
 
   bool isContainer = false;
   mTreeView->IsContainer(mRow, &isContainer);
   if (isContainer) {
     bool isEmpty = false;
     mTreeView->IsContainerEmpty(mRow, &isEmpty);
     if (!isEmpty) {
@@ -1043,17 +1043,17 @@ XULTreeItemAccessibleBase::IsExpandable(
       }
     }
   }
 
   return false;
 }
 
 void
-XULTreeItemAccessibleBase::GetCellName(nsITreeColumn* aColumn, nsAString& aName)
+XULTreeItemAccessibleBase::GetCellName(nsITreeColumn* aColumn, nsAString& aName) const
 {
 
   mTreeView->GetCellText(mRow, aColumn, aName);
 
   // If there is still no name try the cell value:
   // This is for graphical cells. We need tree/table view implementors to
   // implement FooView::GetCellValue to return a meaningful string for cases
   // where there is something shown in the cell (non-text) such as a star icon;
--- a/accessible/xul/XULTreeAccessible.h
+++ b/accessible/xul/XULTreeAccessible.h
@@ -195,22 +195,22 @@ protected:
   virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
                                          nsresult *aError = nullptr) const override;
 
   // XULTreeItemAccessibleBase
 
   /**
    * Return true if the tree item accessible is expandable (contains subrows).
    */
-  bool IsExpandable();
+  bool IsExpandable() const;
 
   /**
    * Return name for cell at the given column.
    */
-  void GetCellName(nsITreeColumn* aColumn, nsAString& aName);
+  void GetCellName(nsITreeColumn* aColumn, nsAString& aName) const;
 
   nsCOMPtr<nsITreeBoxObject> mTree;
   nsITreeView* mTreeView;
   int32_t mRow;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(XULTreeItemAccessibleBase,
                               XULTREEITEMBASEACCESSIBLE_IMPL_CID)
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -1,16 +1,17 @@
 mozilla.pth:python/mach
 mozilla.pth:python/mozboot
 mozilla.pth:python/mozbuild
 mozilla.pth:python/mozlint
 mozilla.pth:python/mozrelease
 mozilla.pth:python/mozterm
 mozilla.pth:python/mozversioncontrol
 mozilla.pth:python/l10n
+mozilla.pth:third_party/python/attrs/src
 mozilla.pth:third_party/python/blessings
 mozilla.pth:third_party/python/compare-locales
 mozilla.pth:third_party/python/configobj
 mozilla.pth:third_party/python/cram
 mozilla.pth:third_party/python/dlmanager
 mozilla.pth:third_party/python/fluent
 mozilla.pth:third_party/python/futures
 mozilla.pth:third_party/python/hglib
--- a/build/win64/mozconfig.asan
+++ b/build/win64/mozconfig.asan
@@ -18,8 +18,9 @@ ac_add_options --enable-address-sanitize
 ac_add_options --enable-debug-symbols
 ac_add_options --disable-install-strip
 ac_add_options --disable-jemalloc
 ac_add_options --disable-crashreporter
 ac_add_options --disable-profiling
 
 . "$topsrcdir/build/mozconfig.vs-common"
 . "$topsrcdir/build/mozconfig.clang-cl"
+. "$topsrcdir/build/mozconfig.lld-link"
--- a/devtools/client/framework/test/browser_target_support.js
+++ b/devtools/client/framework/test/browser_target_support.js
@@ -37,18 +37,16 @@ async function testTarget(client, target
     "target.actorHasMethod() returns false for existing actor with no method");
   hasMethod = await target.actorHasMethod("nope", "nope");
   is(hasMethod, false,
     "target.actorHasMethod() returns false for non-existing actor with no method");
   hasMethod = await target.actorHasMethod();
   is(hasMethod, false,
     "target.actorHasMethod() returns false for undefined params");
 
-  is(target.getTrait("customHighlighters"), true,
-    "target.getTrait() returns boolean when trait exists");
   is(target.getTrait("giddyup"), undefined,
     "target.getTrait() returns undefined when trait does not exist");
 
   close(target, client);
 }
 
 // Ensure target is closed if client is closed directly
 function test() {
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ b/devtools/client/framework/toolbox-highlighter-utils.js
@@ -58,23 +58,16 @@ exports.getHighlighterUtils = function(t
    * which doesn't have the highlighter actor. This can be removed as soon as
    * the minimal supported version becomes 1.4 (29)
    */
   let isRemoteHighlightable = exported.isRemoteHighlightable = function() {
     return target.client.traits.highlightable;
   };
 
   /**
-   * Does the target support custom highlighters.
-   */
-  let supportsCustomHighlighters = exported.supportsCustomHighlighters = () => {
-    return !!target.client.traits.customHighlighters;
-  };
-
-  /**
    * Make a function that initializes the inspector before it runs.
    * Since the init of the inspector is asynchronous, the return value will be
    * produced by Task.async and the argument should be a generator
    * @param {Function*} generator A generator function
    * @return {Function} A function
    */
   let isInspectorInitialized = false;
   let requireInspector = generator => {
@@ -298,21 +291,17 @@ exports.getHighlighterUtils = function(t
    * highlighters are needed in parallel, this method can be used to return a
    * new instance of a highlighter actor, given a type.
    * The type of the highlighter passed must be known by the server.
    * The highlighter actor returned will have the show(nodeFront) and hide()
    * methods and needs to be released by the consumer when not needed anymore.
    * @return a promise that resolves to the highlighter
    */
   exported.getHighlighterByType = requireInspector(async function(typeName) {
-    let highlighter = null;
-
-    if (supportsCustomHighlighters()) {
-      highlighter = await toolbox.inspector.getHighlighterByType(typeName);
-    }
+    let highlighter = await toolbox.inspector.getHighlighterByType(typeName);
 
     return highlighter || promise.reject("The target doesn't support " +
         `creating highlighters by types or ${typeName} is unknown`);
   });
 
   // Return the public API
   return exported;
 };
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -183,44 +183,26 @@ Inspector.prototype = {
   get selection() {
     return this._toolbox.selection;
   },
 
   get highlighter() {
     return this._toolbox.highlighter;
   },
 
-  get isOuterHTMLEditable() {
-    return this._target.client.traits.editOuterHTML;
-  },
-
-  get hasUrlToImageDataResolver() {
-    return this._target.client.traits.urlToImageDataResolver;
-  },
-
-  get canGetUniqueSelector() {
-    return this._target.client.traits.getUniqueSelector;
-  },
-
+  // Added in 53.
   get canGetCssPath() {
     return this._target.client.traits.getCssPath;
   },
 
+  // Added in 56.
   get canGetXPath() {
     return this._target.client.traits.getXPath;
   },
 
-  get canGetUsedFontFaces() {
-    return this._target.client.traits.getUsedFontFaces;
-  },
-
-  get canPasteInnerOrAdjacentHTML() {
-    return this._target.client.traits.pasteHTML;
-  },
-
   /**
    * Handle promise rejections for various asynchronous actions, and only log errors if
    * the inspector panel still exists.
    * This is useful to silence useless errors that happen when the inspector is closed
    * while still initializing (and making protocol requests).
    */
   _handleRejectionIfNotDestroyed: function(e) {
     if (!this._panelDestroyer) {
@@ -836,73 +818,69 @@ Inspector.prototype = {
     // Rules, Layout, Computed, etc.
     if (this.show3PaneToggle) {
       this.sidebar.addExistingTab(
         "computedview",
         INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"),
         defaultTab == "computedview");
     }
 
-    if (this.target.form.animationsActor) {
-      const animationTitle =
-        INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle");
+    const animationTitle =
+      INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle");
 
-      if (Services.prefs.getBoolPref("devtools.new-animationinspector.enabled")) {
-        const animationId = "newanimationinspector";
+    if (Services.prefs.getBoolPref("devtools.new-animationinspector.enabled")) {
+      const animationId = "newanimationinspector";
 
-        this.sidebar.addTab(
-          animationId,
-          animationTitle,
-          {
-            props: {
-              id: animationId,
-              title: animationTitle
-            },
-            panel: () => {
-              const AnimationInspector =
-                this.browserRequire("devtools/client/inspector/animation/animation");
-              this.animationinspector = new AnimationInspector(this, this.panelWin);
-              return this.animationinspector.provider;
-            }
+      this.sidebar.addTab(
+        animationId,
+        animationTitle,
+        {
+          props: {
+            id: animationId,
+            title: animationTitle
           },
-          defaultTab == animationId);
-      } else {
-        this.sidebar.addFrameTab(
-          "animationinspector",
-          animationTitle,
-          "chrome://devtools/content/inspector/animation-old/animation-inspector.xhtml",
-          defaultTab == "animationinspector");
-      }
+          panel: () => {
+            const AnimationInspector =
+              this.browserRequire("devtools/client/inspector/animation/animation");
+            this.animationinspector = new AnimationInspector(this, this.panelWin);
+            return this.animationinspector.provider;
+          }
+        },
+        defaultTab == animationId);
+    } else {
+      this.sidebar.addFrameTab(
+        "animationinspector",
+        animationTitle,
+        "chrome://devtools/content/inspector/animation-old/animation-inspector.xhtml",
+        defaultTab == "animationinspector");
     }
 
-    if (this.canGetUsedFontFaces) {
-      // Inject a lazy loaded react tab by exposing a fake React object
-      // with a lazy defined Tab thanks to `panel` being a function
-      let fontId = "fontinspector";
-      let fontTitle = INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle");
-      this.sidebar.addTab(
-        fontId,
-        fontTitle,
-        {
-          props: {
-            id: fontId,
-            title: fontTitle
-          },
-          panel: () => {
-            if (!this.fontinspector) {
-              const FontInspector =
-                this.browserRequire("devtools/client/inspector/fonts/fonts");
-              this.fontinspector = new FontInspector(this, this.panelWin);
-            }
+    // Inject a lazy loaded react tab by exposing a fake React object
+    // with a lazy defined Tab thanks to `panel` being a function
+    let fontId = "fontinspector";
+    let fontTitle = INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle");
+    this.sidebar.addTab(
+      fontId,
+      fontTitle,
+      {
+        props: {
+          id: fontId,
+          title: fontTitle
+        },
+        panel: () => {
+          if (!this.fontinspector) {
+            const FontInspector =
+              this.browserRequire("devtools/client/inspector/fonts/fonts");
+            this.fontinspector = new FontInspector(this, this.panelWin);
+          }
 
-            return this.fontinspector.provider;
-          }
-        },
-        defaultTab == fontId);
-    }
+          return this.fontinspector.provider;
+        }
+      },
+      defaultTab == fontId);
 
     // Persist splitter state in preferences.
     this.sidebar.on("show", this.onSidebarShown);
     this.sidebar.on("hide", this.onSidebarHidden);
     this.sidebar.on("destroy", this.onSidebarHidden);
 
     this.sidebar.show(defaultTab);
   },
@@ -1196,18 +1174,17 @@ Inspector.prototype = {
     if (this.canAddHTMLChild()) {
       btn.removeAttribute("disabled");
     } else {
       btn.setAttribute("disabled", "true");
     }
 
     // On any new selection made by the user, store the unique css selector
     // of the selected node so it can be restored after reload of the same page
-    if (this.canGetUniqueSelector &&
-        this.selection.isElementNode()) {
+    if (this.selection.isElementNode()) {
       selection.getUniqueSelector().then(selector => {
         this.selectionCssSelector = selector;
       }, this._handleRejectionIfNotDestroyed);
     }
 
     let selfUpdate = this.updating("inspector-panel");
     executeSoon(() => {
       try {
@@ -1415,25 +1392,24 @@ Inspector.prototype = {
     let isSelectionElement = this.selection.isElementNode() &&
                              !this.selection.isPseudoElementNode();
     let isEditableElement = isSelectionElement &&
                             !this.selection.isAnonymousNode();
     let isDuplicatableElement = isSelectionElement &&
                                 !this.selection.isAnonymousNode() &&
                                 !this.selection.isRoot();
     let isScreenshotable = isSelectionElement &&
-                           this.canGetUniqueSelector &&
                            this.selection.nodeFront.isTreeDisplayed;
 
     let menu = new Menu();
     menu.append(new MenuItem({
       id: "node-menu-edithtml",
       label: INSPECTOR_L10N.getStr("inspectorHTMLEdit.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorHTMLEdit.accesskey"),
-      disabled: !isEditableElement || !this.isOuterHTMLEditable,
+      disabled: !isEditableElement,
       click: () => this.editHTML(),
     }));
     menu.append(new MenuItem({
       id: "node-menu-add",
       label: INSPECTOR_L10N.getStr("inspectorAddNode.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorAddNode.accesskey"),
       disabled: !this.canAddHTMLChild(),
       click: () => this.addNode(),
@@ -1611,17 +1587,16 @@ Inspector.prototype = {
       click: () => this.copyOuterHTML(),
     }));
     copySubmenu.append(new MenuItem({
       id: "node-menu-copyuniqueselector",
       label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"),
       accesskey:
         INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"),
       disabled: !isSelectionElement,
-      hidden: !this.canGetUniqueSelector,
       click: () => this.copyUniqueSelector(),
     }));
     copySubmenu.append(new MenuItem({
       id: "node-menu-copycsspath",
       label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"),
       accesskey:
         INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"),
       disabled: !isSelectionElement,
@@ -1645,36 +1620,34 @@ Inspector.prototype = {
       click: () => this.copyImageDataUri(),
     }));
 
     return copySubmenu;
   },
 
   _getPasteSubmenu: function(isEditableElement) {
     let isPasteable = isEditableElement && this._getClipboardContentForPaste();
-    let disableAdjacentPaste = !isPasteable ||
-          !this.canPasteInnerOrAdjacentHTML || this.selection.isRoot() ||
+    let disableAdjacentPaste = !isPasteable || this.selection.isRoot() ||
           this.selection.isBodyNode() || this.selection.isHeadNode();
     let disableFirstLastPaste = !isPasteable ||
-          !this.canPasteInnerOrAdjacentHTML || (this.selection.isHTMLNode() &&
-          this.selection.isRoot());
+          (this.selection.isHTMLNode() && this.selection.isRoot());
 
     let pasteSubmenu = new Menu();
     pasteSubmenu.append(new MenuItem({
       id: "node-menu-pasteinnerhtml",
       label: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.accesskey"),
-      disabled: !isPasteable || !this.canPasteInnerOrAdjacentHTML,
+      disabled: !isPasteable,
       click: () => this.pasteInnerHTML(),
     }));
     pasteSubmenu.append(new MenuItem({
       id: "node-menu-pasteouterhtml",
       label: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.accesskey"),
-      disabled: !isPasteable || !this.isOuterHTMLEditable,
+      disabled: !isPasteable,
       click: () => this.pasteOuterHTML(),
     }));
     pasteSubmenu.append(new MenuItem({
       id: "node-menu-pastebefore",
       label: INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.label"),
       accesskey:
         INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.accesskey"),
       disabled: disableAdjacentPaste,
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -234,26 +234,22 @@ CssRuleView.prototype = {
    *
    * @return {Promise} Resolves to the instance of the highlighter.
    */
   async getSelectorHighlighter() {
     if (!this.inspector) {
       return null;
     }
 
-    let utils = this.inspector.toolbox.highlighterUtils;
-    if (!utils.supportsCustomHighlighters()) {
-      return null;
-    }
-
     if (this.selectorHighlighter) {
       return this.selectorHighlighter;
     }
 
     try {
+      let utils = this.inspector.toolbox.highlighterUtils;
       let h = await utils.getHighlighterByType("SelectorHighlighter");
       this.selectorHighlighter = h;
       return h;
     } catch (e) {
       // The SelectorHighlighter type could not be created in the
       // current target.  It could be an older server, or a XUL page.
       return null;
     }
@@ -540,23 +536,18 @@ CssRuleView.prototype = {
   },
 
   /**
    * Add a new rule to the current element.
    */
   _onAddRule: function() {
     let elementStyle = this._elementStyle;
     let element = elementStyle.element;
-    let client = this.inspector.target.client;
     let pseudoClasses = element.pseudoClassLocks;
 
-    if (!client.traits.addNewRule) {
-      return;
-    }
-
     if (!this.pageStyle.supportsAuthoredStyles) {
       // We're talking to an old server.
       this._onAddNewRuleNonAuthored();
       return;
     }
 
     // Adding a new rule with authored styles will cause the actor to
     // emit an event, which will in turn cause the rule view to be
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -43,19 +43,16 @@ class HighlightersOverlay {
     * behave like highlighters but with added editing capabilities that need to map value
     * changes to properties in the Rule view.
     */
     this.editors = {};
     this.inspector = inspector;
     this.highlighterUtils = this.inspector.toolbox.highlighterUtils;
     this.store = this.inspector.store;
 
-    // Only initialize the overlay if at least one of the highlighter types is supported.
-    this.supportsHighlighters = this.highlighterUtils.supportsCustomHighlighters();
-
     // NodeFront of the flexbox container that is highlighted.
     this.flexboxHighlighterShown = null;
     // NodeFront of element that is highlighted by the geometry editor.
     this.geometryEditorHighlighterShown = null;
     // NodeFront of the grid container that is highlighted.
     this.gridHighlighterShown = null;
     // Name of the highlighter shown on mouse hover.
     this.hoveredHighlighterShown = null;
@@ -107,40 +104,32 @@ class HighlightersOverlay {
   /**
    * Add the highlighters overlay to the view. This will start tracking mouse events
    * and display highlighters when needed.
    *
    * @param  {CssRuleView|CssComputedView|LayoutView} view
    *         Either the rule-view or computed-view panel to add the highlighters overlay.
    */
   addToView(view) {
-    if (!this.supportsHighlighters) {
-      return;
-    }
-
     let el = view.element;
     el.addEventListener("click", this.onClick, true);
     el.addEventListener("mousemove", this.onMouseMove);
     el.addEventListener("mouseout", this.onMouseOut);
     el.ownerDocument.defaultView.addEventListener("mouseout", this.onMouseOut);
   }
 
   /**
    * Remove the overlay from the given view. This will stop tracking mouse movement and
    * showing highlighters.
    *
    * @param  {CssRuleView|CssComputedView|LayoutView} view
    *         Either the rule-view or computed-view panel to remove the highlighters
    *         overlay.
    */
   removeFromView(view) {
-    if (!this.supportsHighlighters) {
-      return;
-    }
-
     let el = view.element;
     el.removeEventListener("click", this.onClick, true);
     el.removeEventListener("mousemove", this.onMouseMove);
     el.removeEventListener("mouseout", this.onMouseOut);
   }
 
   /**
    * Load the grid highligher display settings into the store from the stored preferences.
@@ -989,17 +978,16 @@ class HighlightersOverlay {
     // Remove inspector events.
     this.inspector.off("markupmutation", this.onMarkupMutation);
     this.inspector.target.off("will-navigate", this.onWillNavigate);
 
     this._lastHovered = null;
 
     this.inspector = null;
     this.highlighterUtils = null;
-    this.supportsHighlighters = null;
     this.state = null;
     this.store = null;
 
     this.boxModelHighlighterShown = null;
     this.flexboxHighlighterShown = null;
     this.geometryEditorHighlighterShown = null;
     this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
--- a/devtools/client/inspector/shared/tooltips-overlay.js
+++ b/devtools/client/inspector/shared/tooltips-overlay.js
@@ -157,21 +157,19 @@ TooltipsOverlay.prototype = {
    * Given a hovered node info, find out which type of tooltip should be shown,
    * if any
    *
    * @param {Object} nodeInfo
    * @return {String} The tooltip type to be shown, or null
    */
   _getTooltipType: function({type, value: prop}) {
     let tooltipType = null;
-    let inspector = this.view.inspector;
 
     // Image preview tooltip
-    if (type === VIEW_NODE_IMAGE_URL_TYPE &&
-        inspector.hasUrlToImageDataResolver) {
+    if (type === VIEW_NODE_IMAGE_URL_TYPE) {
       tooltipType = TOOLTIP_IMAGE_TYPE;
     }
 
     // Font preview tooltip
     if ((type === VIEW_NODE_VALUE_TYPE && prop.property === "font-family") ||
         (type === VIEW_NODE_FONT_TYPE)) {
       let value = prop.value.toLowerCase();
       if (value !== "inherit" && value !== "unset" && value !== "initial") {
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -123,27 +123,26 @@ StyleEditorUI.prototype = {
   },
 
   async initializeHighlighter() {
     let toolbox = gDevTools.getToolbox(this._target);
     await toolbox.initInspector();
     this._walker = toolbox.walker;
 
     let hUtils = toolbox.highlighterUtils;
-    if (hUtils.supportsCustomHighlighters()) {
-      try {
-        this._highlighter =
-          await hUtils.getHighlighterByType(SELECTOR_HIGHLIGHTER_TYPE);
-      } catch (e) {
-        // The selectorHighlighter can't always be instantiated, for example
-        // it doesn't work with XUL windows (until bug 1094959 gets fixed);
-        // or the selectorHighlighter doesn't exist on the backend.
-        console.warn("The selectorHighlighter couldn't be instantiated, " +
-          "elements matching hovered selectors will not be highlighted");
-      }
+
+    try {
+      this._highlighter =
+        await hUtils.getHighlighterByType(SELECTOR_HIGHLIGHTER_TYPE);
+    } catch (e) {
+      // The selectorHighlighter can't always be instantiated, for example
+      // it doesn't work with XUL windows (until bug 1094959 gets fixed);
+      // or the selectorHighlighter doesn't exist on the backend.
+      console.warn("The selectorHighlighter couldn't be instantiated, " +
+        "elements matching hovered selectors will not be highlighted");
     }
   },
 
   /**
    * Build the initial UI and wire buttons with event handlers.
    */
   createUI: function() {
     let viewRoot = this._root.parentNode.querySelector(".splitview-root");
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -107,65 +107,45 @@ function RootActor(connection, parameter
 }
 
 RootActor.prototype = {
   constructor: RootActor,
   applicationType: "browser",
 
   traits: {
     sources: true,
-    // Whether the inspector actor allows modifying outer HTML.
-    editOuterHTML: true,
-    // Whether the inspector actor allows modifying innerHTML and inserting
-    // adjacent HTML.
-    pasteHTML: true,
     // Whether the server-side highlighter actor exists and can be used to
     // remotely highlight nodes (see server/actors/highlighters.js)
     highlightable: true,
-    // Which custom highlighter does the server-side highlighter actor supports?
-    // (see server/actors/highlighters.js)
-    customHighlighters: true,
-    // Whether the inspector actor implements the getImageDataFromURL
-    // method that returns data-uris for image URLs. This is used for image
-    // tooltips for instance
-    urlToImageDataResolver: true,
     networkMonitor: true,
     // Whether the storage inspector actor to inspect cookies, etc.
     storageInspector: true,
     // Whether storage inspector is read only
     storageInspectorReadOnly: true,
     // Whether conditional breakpoints are supported
     conditionalBreakpoints: true,
     // Whether the server supports full source actors (breakpoints on
     // eval scripts, etc)
     debuggerSourceActors: true,
     // Whether the server can return wasm binary source
     wasmBinarySource: true,
     bulk: true,
     // Whether the style rule actor implements the modifySelector method
     // that modifies the rule's selector
     selectorEditable: true,
-    // Whether the page style actor implements the addNewRule method that
-    // adds new rules to the page
-    addNewRule: true,
-    // Whether the dom node actor implements the getUniqueSelector method
-    getUniqueSelector: true,
-    // Whether the dom node actor implements the getCssPath method
+    // Whether the dom node actor implements the getCssPath method. Added in 53.
     getCssPath: true,
-    // Whether the dom node actor implements the getXPath method
+    // Whether the dom node actor implements the getXPath method. Added in 56.
     getXPath: true,
     // Whether the director scripts are supported
     directorScripts: true,
     // Whether the debugger server supports
     // blackboxing/pretty-printing (not supported in Fever Dream yet)
     noBlackBoxing: false,
     noPrettyPrinting: false,
-    // Whether the page style actor implements the getUsedFontFaces method
-    // that returns the font faces used on a node
-    getUsedFontFaces: true,
     // Trait added in Gecko 38, indicating that all features necessary for
     // grabbing allocations from the MemoryActor are available for the performance tool
     memoryActorAllocations: true,
     // Added in Firefox 40. Indicates that the backend supports registering custom
     // commands through the WebConsoleCommands API.
     webConsoleCommands: true,
     // Whether root actor exposes tab actors and access to any window.
     // If allowChromeProcess is true, you can:
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1086,17 +1086,21 @@ nsIContent::GetEventTargetParent(EventCh
             //  parent to null."
             aVisitor.IgnoreCurrentTarget();
             // Old code relies on mTarget to point to the first element which
             // was not added to the event target chain because of mCanHandle
             // being false, but in Shadow DOM case mTarget really should
             // point to a node in Shadow DOM.
             aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope;
             return;
-          } else {
+          } else if (targetInKnownToBeHandledScope) {
+            // Note, if targetInKnownToBeHandledScope is null,
+            // mTargetInKnownToBeHandledScope could be Window object in content
+            // page and we're in chrome document in the same process.
+
             // Step 11.6
             aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
           }
         }
       }
     }
   }
 
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -6505,51 +6505,33 @@ int32_t
 nsGlobalWindowInner::SetTimeout(JSContext* aCx, const nsAString& aHandler,
                                 int32_t aTimeout,
                                 const Sequence<JS::Value>& /* unused */,
                                 ErrorResult& aError)
 {
   return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aError);
 }
 
-static bool
-IsInterval(const Optional<int32_t>& aTimeout, int32_t& aResultTimeout)
-{
-  if (aTimeout.WasPassed()) {
-    aResultTimeout = aTimeout.Value();
-    return true;
-  }
-
-  // If no interval was specified, treat this like a timeout, to avoid setting
-  // an interval of 0 milliseconds.
-  aResultTimeout = 0;
-  return false;
-}
-
 int32_t
 nsGlobalWindowInner::SetInterval(JSContext* aCx, Function& aFunction,
-                                 const Optional<int32_t>& aTimeout,
+                                 const int32_t aTimeout,
                                  const Sequence<JS::Value>& aArguments,
                                  ErrorResult& aError)
 {
-  int32_t timeout;
-  bool isInterval = IsInterval(aTimeout, timeout);
-  return SetTimeoutOrInterval(aCx, aFunction, timeout, aArguments, isInterval,
-                              aError);
+  return SetTimeoutOrInterval(
+    aCx, aFunction, aTimeout, aArguments, true, aError);
 }
 
 int32_t
 nsGlobalWindowInner::SetInterval(JSContext* aCx, const nsAString& aHandler,
-                                 const Optional<int32_t>& aTimeout,
+                                 const int32_t aTimeout,
                                  const Sequence<JS::Value>& /* unused */,
                                  ErrorResult& aError)
 {
-  int32_t timeout;
-  bool isInterval = IsInterval(aTimeout, timeout);
-  return SetTimeoutOrInterval(aCx, aHandler, timeout, isInterval, aError);
+  return SetTimeoutOrInterval(aCx, aHandler, aTimeout, true, aError);
 }
 
 int32_t
 nsGlobalWindowInner::SetTimeoutOrInterval(JSContext *aCx, Function& aFunction,
                                           int32_t aTimeout,
                                           const Sequence<JS::Value>& aArguments,
                                           bool aIsInterval, ErrorResult& aError)
 {
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -322,17 +322,17 @@ public:
 
   bool ComputeDefaultWantsUntrusted(mozilla::ErrorResult& aRv) final;
 
   virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
 
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   EventTarget* GetTargetForDOMEvent() override;
-  
+
   using mozilla::dom::EventTarget::DispatchEvent;
   bool DispatchEvent(mozilla::dom::Event& aEvent,
                      mozilla::dom::CallerType aCallerType,
                      mozilla::ErrorResult& aRv) override;
 
   void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
 
   nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
@@ -741,21 +741,21 @@ public:
                      const mozilla::dom::Sequence<JS::Value>& aArguments,
                      mozilla::ErrorResult& aError);
   int32_t SetTimeout(JSContext* aCx, const nsAString& aHandler,
                      int32_t aTimeout,
                      const mozilla::dom::Sequence<JS::Value>& /* unused */,
                      mozilla::ErrorResult& aError);
   void ClearTimeout(int32_t aHandle);
   int32_t SetInterval(JSContext* aCx, mozilla::dom::Function& aFunction,
-                      const mozilla::dom::Optional<int32_t>& aTimeout,
+                      const int32_t aTimeout,
                       const mozilla::dom::Sequence<JS::Value>& aArguments,
                       mozilla::ErrorResult& aError);
   int32_t SetInterval(JSContext* aCx, const nsAString& aHandler,
-                      const mozilla::dom::Optional<int32_t>& aTimeout,
+                      const int32_t aTimeout,
                       const mozilla::dom::Sequence<JS::Value>& /* unused */,
                       mozilla::ErrorResult& aError);
   void ClearInterval(int32_t aHandle);
   void GetOrigin(nsAString& aOrigin);
   void Atob(const nsAString& aAsciiBase64String, nsAString& aBinaryData,
             mozilla::ErrorResult& aError);
   void Btoa(const nsAString& aBinaryData, nsAString& aAsciiBase64String,
             mozilla::ErrorResult& aError);
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -651,16 +651,36 @@ MayRetargetToChromeIfCanNotHandleEvent(
     if (chromeTargetEtci) {
       chromeTargetEtci->GetEventTargetParent(aPreVisitor);
       return chromeTargetEtci;
     }
   }
   return nullptr;
 }
 
+static bool
+ShouldClearTargets(WidgetEvent* aEvent)
+{
+  nsCOMPtr<nsIContent> finalTarget;
+  nsCOMPtr<nsIContent> finalRelatedTarget;
+  if ((finalTarget = do_QueryInterface(aEvent->mTarget)) &&
+      finalTarget->SubtreeRoot()->IsShadowRoot()) {
+    return true;
+  }
+
+  if ((finalRelatedTarget =
+         do_QueryInterface(aEvent->mRelatedTarget)) &&
+      finalRelatedTarget->SubtreeRoot()->IsShadowRoot()) {
+    return true;
+  }
+  //XXXsmaug Check also all the touch objects.
+
+  return false;
+}
+
 /* static */ nsresult
 EventDispatcher::Dispatch(nsISupports* aTarget,
                           nsPresContext* aPresContext,
                           WidgetEvent* aEvent,
                           Event* aDOMEvent,
                           nsEventStatus* aEventStatus,
                           EventDispatchingCallback* aCallback,
                           nsTArray<EventTarget*>* aTargets)
@@ -831,16 +851,18 @@ EventDispatcher::Dispatch(nsISupports* a
     NS_ENSURE_STATE(aEvent->mOriginalTarget);
   }
   else {
     aEvent->mOriginalTarget = aEvent->mTarget;
   }
 
   aEvent->mOriginalRelatedTarget = aEvent->mRelatedTarget;
 
+  bool clearTargets = false;
+
   nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mOriginalTarget);
   bool isInAnon = content && content->IsInAnonymousSubtree();
 
   aEvent->mFlags.mIsBeingDispatched = true;
 
   // Create visitor object and start event dispatching.
   // GetEventTargetParent for the original target.
   nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore;
@@ -854,16 +876,18 @@ EventDispatcher::Dispatch(nsISupports* a
                                                         content);
   }
   if (!preVisitor.mCanHandle) {
     // The original target and chrome target (mAutomaticChromeDispatch=true)
     // can not handle the event but we still have to call their PreHandleEvent.
     for (uint32_t i = 0; i < chain.Length(); ++i) {
       chain[i].PreHandleEvent(preVisitor);
     }
+
+    clearTargets = ShouldClearTargets(aEvent);
   } else {
     // At least the original target can handle the event.
     // Setting the retarget to the |target| simplifies retargeting code.
     nsCOMPtr<EventTarget> t = do_QueryInterface(aEvent->mTarget);
     targetEtci->SetNewTarget(t);
     EventTargetChainItem* topEtci = targetEtci;
     targetEtci = nullptr;
     while (preVisitor.GetParentTarget()) {
@@ -922,16 +946,19 @@ EventDispatcher::Dispatch(nsISupports* a
         for (uint32_t i = 0; i < numTargets; ++i) {
           targets[i] = chain[i].CurrentTarget()->GetTargetForDOMEvent();
         }
       } else {
         // Event target chain is created. PreHandle the chain.
         for (uint32_t i = 0; i < chain.Length(); ++i) {
           chain[i].PreHandleEvent(preVisitor);
         }
+
+        clearTargets = ShouldClearTargets(aEvent);
+
         // Handle the chain.
         EventChainPostVisitor postVisitor(preVisitor);
         MOZ_RELEASE_ASSERT(!aEvent->mPath);
         aEvent->mPath = &chain;
         EventTargetChainItem::HandleEventTargetChain(chain, postVisitor,
                                                      aCallback, cd);
         aEvent->mPath = nullptr;
 
@@ -946,25 +973,26 @@ EventDispatcher::Dispatch(nsISupports* a
 
   // Note, EventTargetChainItem objects are deleted when the chain goes out of
   // the scope.
 
   aEvent->mFlags.mIsBeingDispatched = false;
   aEvent->mFlags.mDispatchedAtLeastOnce = true;
 
   // https://dom.spec.whatwg.org/#concept-event-dispatch
-  // Step 18
-  // "If target's root is a shadow root, then set event's target attribute and
-  //  event's relatedTarget to null."
-  nsCOMPtr<nsIContent> finalTarget = do_QueryInterface(aEvent->mTarget);
-  if (finalTarget && finalTarget->SubtreeRoot()->IsShadowRoot()) {
+  // step 10. If clearTargets, then:
+  //          1. Set event's target to null.
+  //          2. Set event's relatedTarget to null.
+  //          3. Set event's touch target list to the empty list.
+  if (clearTargets) {
     aEvent->mTarget = nullptr;
     aEvent->mOriginalTarget = nullptr;
     aEvent->mRelatedTarget = nullptr;
     aEvent->mOriginalRelatedTarget = nullptr;
+    //XXXsmaug Check also all the touch objects.
   }
 
   if (!externalDOMEvent && preVisitor.mDOMEvent) {
     // An dom::Event was created while dispatching the event.
     // Duplicate private data if someone holds a pointer to it.
     nsrefcnt rc = 0;
     NS_RELEASE2(preVisitor.mDOMEvent, rc);
     if (preVisitor.mDOMEvent) {
--- a/dom/webidl/WindowOrWorkerGlobalScope.webidl
+++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl
@@ -25,19 +25,19 @@ interface WindowOrWorkerGlobalScope {
   // NOTE: We're using overloads where the spec uses a union.  Should
   // be black-box the same.
   [Throws]
   long setTimeout(Function handler, optional long timeout = 0, any... arguments);
   [Throws]
   long setTimeout(DOMString handler, optional long timeout = 0, any... unused);
   void clearTimeout(optional long handle = 0);
   [Throws]
-  long setInterval(Function handler, optional long timeout, any... arguments);
+  long setInterval(Function handler, optional long timeout = 0, any... arguments);
   [Throws]
-  long setInterval(DOMString handler, optional long timeout, any... unused);
+  long setInterval(DOMString handler, optional long timeout = 0, any... unused);
   void clearInterval(optional long handle = 0);
 
   // ImageBitmap
   [Throws]
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage);
   [Throws]
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh);
 };
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -300,51 +300,45 @@ WorkerGlobalScope::ClearTimeout(int32_t 
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   mWorkerPrivate->ClearTimeout(aHandle);
 }
 
 int32_t
 WorkerGlobalScope::SetInterval(JSContext* aCx,
                                Function& aHandler,
-                               const Optional<int32_t>& aTimeout,
+                               const int32_t aTimeout,
                                const Sequence<JS::Value>& aArguments,
                                ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
-  bool isInterval = aTimeout.WasPassed();
-  int32_t timeout = aTimeout.WasPassed() ? aTimeout.Value() : 0;
-
   nsCOMPtr<nsIScriptTimeoutHandler> handler =
     NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler, aArguments, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return 0;
   }
 
-  return mWorkerPrivate->SetTimeout(aCx, handler,  timeout, isInterval, aRv);
+  return mWorkerPrivate->SetTimeout(aCx, handler,  aTimeout, true, aRv);
 }
 
 int32_t
 WorkerGlobalScope::SetInterval(JSContext* aCx,
                                const nsAString& aHandler,
-                               const Optional<int32_t>& aTimeout,
+                               const int32_t aTimeout,
                                const Sequence<JS::Value>& /* unused */,
                                ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   Sequence<JS::Value> dummy;
 
-  bool isInterval = aTimeout.WasPassed();
-  int32_t timeout = aTimeout.WasPassed() ? aTimeout.Value() : 0;
-
   nsCOMPtr<nsIScriptTimeoutHandler> handler =
     NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler);
-  return mWorkerPrivate->SetTimeout(aCx, handler, timeout, isInterval, aRv);
+  return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, true, aRv);
 }
 
 void
 WorkerGlobalScope::ClearInterval(int32_t aHandle)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   mWorkerPrivate->ClearTimeout(aHandle);
 }
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -129,21 +129,21 @@ public:
              const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
   int32_t
   SetTimeout(JSContext* aCx, const nsAString& aHandler, const int32_t aTimeout,
              const Sequence<JS::Value>& /* unused */, ErrorResult& aRv);
   void
   ClearTimeout(int32_t aHandle);
   int32_t
   SetInterval(JSContext* aCx, Function& aHandler,
-              const Optional<int32_t>& aTimeout,
+              const int32_t aTimeout,
               const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
   int32_t
   SetInterval(JSContext* aCx, const nsAString& aHandler,
-              const Optional<int32_t>& aTimeout,
+              const int32_t aTimeout,
               const Sequence<JS::Value>& /* unused */, ErrorResult& aRv);
   void
   ClearInterval(int32_t aHandle);
 
   void
   GetOrigin(nsAString& aOrigin) const;
 
   void
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52616
+52622
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -4626,16 +4626,17 @@ Gandhi/M
 Gandhian/M
 Ganesha/M
 Ganges/M
 Gangtok/M
 Gannon/M
 Gantry/M
 Ganymede/M
 Gap/M
+Garamond
 Garbo/M
 Garcia/M
 Gard
 Gardiner
 Gardner/M
 Gare/MH
 Gareth/M
 Garey/M
@@ -5159,16 +5160,17 @@ HRH
 HS
 HSBC/M
 HST
 HT
 HTML/M
 HTTP
 HTTPS
 HUD/M
+HVAC
 Ha/M
 Haas/M
 Habakkuk/M
 Haber/M
 Had's
 Hadar/M
 Hades/M
 Hadleigh/M
@@ -15762,16 +15764,17 @@ backside/SM
 backslapper/SM
 backslapping/M
 backslash/MS
 backslid
 backslide/RSZG
 backslider/M
 backspace/DSMG
 backspin/M
+backsplash/S
 backstabber/MS
 backstabbing
 backstage/M
 backstair/S
 backstop/SM
 backstopped
 backstopping
 backstory/S
@@ -32213,17 +32216,19 @@ kedgeree
 keel/MDSG
 keelhaul/DGS
 keen/MDRYSTGP
 keenness/M
 keep/MRSZG
 keeper/M
 keeping/M
 keepsake/MS
+keester/S
 keg/SM
+keister/S
 kelp/M
 kelvin/SM
 ken/SM
 kenned
 kennel/SGMD
 kenning
 keno/M
 kepi/MS
@@ -47669,16 +47674,17 @@ swipe/DSMG
 swirl/GSMD
 swirly
 swish/TGMDRS
 switch/MDRSZGB
 switchback/MS
 switchblade/SM
 switchboard/SM
 switcher/M
+switcheroo/S
 swivel/MDGS
 swiz
 swizz
 swizzle/DSG
 swollen
 swoon/SGMD
 swoop/SGMD
 swoosh/MDSG
--- a/gfx/tests/mochitest/mochitest.ini
+++ b/gfx/tests/mochitest/mochitest.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 
 [test_acceleration.html]
 subsuite = gpu
 fail-if = (os == "win" && os_version == "5.1" && e10s) # Bug 1253862
 [test_bug509244.html]
 [test_bug513439.html]
 [test_font_whitelist.html]
+skip-if = (os == "win" && asan) # Bug 1458364
--- a/layout/generic/DetailsFrame.cpp
+++ b/layout/generic/DetailsFrame.cpp
@@ -125,15 +125,28 @@ DetailsFrame::AppendAnonymousContentTo(n
   if (mDefaultSummary) {
     aElements.AppendElement(mDefaultSummary);
   }
 }
 
 bool
 DetailsFrame::HasMainSummaryFrame(nsIFrame* aSummaryFrame)
 {
-  nsIFrame* firstChild =
-    nsPlaceholderFrame::GetRealFrameFor(mFrames.FirstChild());
-
+  nsIFrame* firstChild = nullptr;
+  for (nsIFrame* frag = this; frag; frag = frag->GetNextInFlow()) {
+    firstChild = frag->PrincipalChildList().FirstChild();
+    if (!firstChild) {
+      nsFrameList* overflowFrames = GetOverflowFrames();
+      if (overflowFrames) {
+        firstChild = overflowFrames->FirstChild();
+      }
+    }
+    if (firstChild) {
+      firstChild = nsPlaceholderFrame::GetRealFrameFor(firstChild);
+      MOZ_ASSERT(firstChild && firstChild->IsPrimaryFrame(),
+                 "this is probably not the frame you were looking for");
+      break;
+    }
+  }
   return aSummaryFrame == firstChild;
 }
 
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1458028.html
@@ -0,0 +1,13 @@
+<style>
+.cl { page-break-inside: avoid; }
+summary::first-letter { float: right; }
+</style>
+<script>
+function go() {
+  a.remove();
+}
+</script>
+<body onload=go()>
+<details>
+<summary id="a" class="cl">
+T
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -676,8 +676,9 @@ load 1401709.html
 load 1401807.html
 load 1404222-empty-shape.html
 load 1405443.html
 load 1415185.html
 load 1416544.html
 load 1427824.html
 load 1431781.html
 load 1431781-2.html
+asserts(6) load 1458028.html # bug 847368
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -197,16 +197,17 @@ skip-if = toolkit == 'android' #bug 5366
 [test_css_supports.html]
 [test_css_supports_variables.html]
 [test_custom_content_inheritance.html]
 [test_default_bidi_css.html]
 [test_default_computed_style.html]
 [test_descriptor_storage.html]
 [test_descriptor_syntax_errors.html]
 [test_dont_use_document_colors.html]
+skip-if = (os == 'win' && asan) # Bug 1458365
 [test_dynamic_change_causing_reflow.html]
 [test_exposed_prop_accessors.html]
 [test_extra_inherit_initial.html]
 [test_flexbox_child_display_values.xhtml]
 [test_flexbox_flex_grow_and_shrink.html]
 [test_flexbox_flex_shorthand.html]
 [test_flexbox_focus_order.html]
 [test_flexbox_layout.html]
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1260,22 +1260,21 @@ class PackageFrontend(MachCommandBase):
             open_manifest,
             unpack_file,
         )
         from requests.adapters import HTTPAdapter
         import redo
         import requests
         import shutil
 
-        from taskgraph.generator import load_graph_config, Kind
+        from taskgraph.config import load_graph_config
+        from taskgraph.generator import Kind
         from taskgraph.util.taskcluster import (
             get_artifact_url,
-            list_artifacts,
         )
-        import yaml
 
         self._set_log_level(verbose)
         # Normally, we'd use self.log_manager.enable_unstructured(),
         # but that enables all logging, while we only really want tooltool's
         # and it also makes structured log output twice.
         # So we manually do what it does, and limit that to the tooltool
         # logger.
         if self.log_manager.terminal_handler:
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -99,16 +99,22 @@ try:
             - 'sm-mozjs-sys-linux64'
             - 'sm-msan-linux64'
             - 'sm-fuzzing-linux64'
             - 'sm-rust-bindings-linux64'
         'win32':
             - 'sm-plain-win32'
             - 'sm-compacting-win32'
 
+release-promotion:
+    products:
+        - 'devedition'
+        - 'fennec'
+        - 'firefox'
+
 scriptworker:
     # See additional configuration in taskcluster/taskgraph/util/scriptworker.py
     scope-prefix: 'project:releng'
     worker-types:
         'scriptworker-prov-v1/signing-linux-v1':
             - 'project:releng:signing:cert:release-signing'
             - 'project:releng:signing:cert:nightly-signing'
         'scriptworker-prov-v1/depsigning':
--- a/taskcluster/ci/release-secondary-balrog-scheduling/kind.yml
+++ b/taskcluster/ci/release-secondary-balrog-scheduling/kind.yml
@@ -1,32 +1,25 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
-   - taskgraph.transforms.worker_type:transforms
+   - taskgraph.transforms.scriptworker:add_balrog_scopes
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - post-balrog-dummy
    - post-beetmover-dummy
    - release-secondary-balrog-submit-toplevel
 
 job-defaults:
-   worker-type:
-      by-project:
-         maple: scriptworker-prov-v1/balrog-dev
-         birch: scriptworker-prov-v1/balrog-dev
-         mozilla-beta: scriptworker-prov-v1/balrogworker-v1
-         mozilla-release: scriptworker-prov-v1/balrogworker-v1
-         default: invalid/invalid
    run-on-projects: []
    shipping-phase: ship
    worker:
       implementation: balrog
       balrog-action: schedule
 
 jobs:
    firefox-secondary-rc:
--- a/taskcluster/ci/release-secondary-balrog-submit-toplevel/kind.yml
+++ b/taskcluster/ci/release-secondary-balrog-submit-toplevel/kind.yml
@@ -1,29 +1,22 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
-   - taskgraph.transforms.worker_type:transforms
+   - taskgraph.transforms.scriptworker:add_balrog_scopes
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    run-on-projects: []  # to make sure this never runs in CI
    shipping-phase: promote
-   worker-type:
-      by-project:
-         maple: scriptworker-prov-v1/balrog-dev
-         birch: scriptworker-prov-v1/balrog-dev
-         mozilla-beta: scriptworker-prov-v1/balrogworker-v1
-         mozilla-release: scriptworker-prov-v1/balrogworker-v1
-         default: invalid/invalid
    worker:
       implementation: balrog
       balrog-action: submit-toplevel
       require-mirrors: false
       platforms: ["linux", "linux64", "macosx64", "win32", "win64"]
 
 jobs:
    firefox:
--- a/taskcluster/ci/test/talos.yml
+++ b/taskcluster/ci/test/talos.yml
@@ -33,31 +33,27 @@ talos-chrome:
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 1200
     mozharness:
         extra-options:
             - --suite=chromez
-            - --add-option
-            - --webServer,localhost
 
 talos-chrome-profiling:
     description: "Talos profiling chrome"
     try-name: chromez-profiling
     treeherder-symbol: T-P(c)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1200
     mozharness:
         extra-options:
             - --suite=chromez
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-damp:
     description: "Talos devtools (damp)"
     try-name: damp
     treeherder-symbol: T(damp)
     max-run-time:
         by-test-platform:
             linux64.*: 2700
@@ -65,213 +61,185 @@ talos-damp:
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             linux64-ccov/.*: ['try']  # Bug 1407593
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     mozharness:
         extra-options:
             - --suite=damp
-            - --add-option
-            - --webServer,localhost
 
 talos-damp-profiling:
     description: "Talos profiling devtools"
     try-name: damp-profiling
     treeherder-symbol: T-P(damp)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1200
     mozharness:
         extra-options:
             - --suite=damp
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-dromaeojs:
     description: "Talos dromaeojs"
     try-name: dromaeojs
     treeherder-symbol: T(d)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 2100
     mozharness:
         extra-options:
             - --suite=dromaeojs
-            - --add-option
-            - --webServer,localhost
 
 talos-dromaeojs-profiling:
     description: "Talos profiling dromaeojs"
     try-name: dromaeojs-profiling
     treeherder-symbol: T-P(d)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 2100
     mozharness:
         extra-options:
             - --suite=dromaeojs
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-flex:
     description: "Talos XUL flexbox emulation enabled"
     try-name: flex
     treeherder-symbol: T(f)
     tier: 3
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1800
     mozharness:
         extra-options:
             - --suite=flex
-            - --add-option
-            - --webServer,localhost
 
 talos-g1:
     description: "Talos g1"
     try-name: g1
     treeherder-symbol: T(g1)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time:
         by-test-platform:
             linux64.*: 2700
             default: 7200
     mozharness:
         extra-options:
             - --suite=g1
-            - --add-option
-            - --webServer,localhost
 
 talos-g1-profiling:
     description: "Talos profiling g1"
     try-name: g1-profiling
     treeherder-symbol: T-P(g1)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time:
         by-test-platform:
             linux64.*: 2700
             default: 7200
     mozharness:
         extra-options:
             - --suite=g1
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-g3:
     description: "Talos g3"
     try-name: g3
     treeherder-symbol: T(g3)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 900
     mozharness:
         extra-options:
             - --suite=g3
-            - --add-option
-            - --webServer,localhost
 
 talos-g3-profiling:
     description: "Talos profiling g3"
     try-name: g3-profiling
     treeherder-symbol: T-P(g3)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 900
     mozharness:
         extra-options:
             - --suite=g3
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-g4:
     description: "Talos g4"
     try-name: g4
     treeherder-symbol: T(g4)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time:
         by-test-platform:
             linux64.*: 1500
             default: 1800
     mozharness:
         extra-options:
             - --suite=g4
-            - --add-option
-            - --webServer,localhost
 
 talos-g4-profiling:
     description: "Talos profiling g4"
     try-name: g4-profiling
     treeherder-symbol: T-P(g4)
     run-on-projects: ['mozilla-central', 'try']
     mozharness:
         extra-options:
             - --suite=g4
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
     max-run-time:
         by-test-platform:
             linux64.*: 1500
             default: 1800
 
 talos-g5:
     description: "Talos g5"
     try-name: g5
     treeherder-symbol: T(g5)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     mozharness:
         extra-options:
             - --suite=g5
-            - --add-option
-            - --webServer,localhost
     max-run-time:
         by-test-platform:
             linux64.*: 1200
             default: 1800
 
 talos-g5-profiling:
     description: "Talos profiling g5"
     try-name: g5-profiling
     treeherder-symbol: T-P(g5)
     run-on-projects: ['mozilla-central', 'try']
     mozharness:
         extra-options:
             - --suite=g5
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
     max-run-time:
         by-test-platform:
             linux64.*: 1200
             default: 1800
 
 talos-h1:
     description: "Talos h1"
     try-name: h1
     treeherder-symbol: T(h1)
     run-on-projects: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     mozharness:
         extra-options:
             - --suite=h1
-            - --add-option
-            - --webServer,localhost
     max-run-time:
         by-test-platform:
             linux64.*: 900
             default: 1800
 
 talos-h2:
     description: "Talos h2"
     try-name: h2
@@ -279,81 +247,71 @@ talos-h2:
     max-run-time:
         by-test-platform:
             linux64.*: 900
             default: 7200
     run-on-projects: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     mozharness:
         extra-options:
             - --suite=h2
-            - --add-option
-            - --webServer,localhost
 
 talos-motionmark:
     description: "Talos motionmark"
     try-name: motionmark
     treeherder-symbol: T(mm)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 3600
     tier:
         by-test-platform:
             windows10-64-ccov/.*: 3
             linux64-ccov/.*: 3
             default: 2
     mozharness:
         extra-options:
             - --suite=motionmark
-            - --add-option
-            - --webServer,localhost
 
 talos-motionmark-profiling:
     description: "Talos profiling motionmark"
     try-name: motionmark-profiling
     treeherder-symbol: T-P(mm)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 3600
     tier:
         by-test-platform:
             windows10-64-ccov/.*: 3
             linux64-ccov/.*: 3
             default: 2
     mozharness:
         extra-options:
             - --suite=motionmark
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-other:
     description: "Talos other"
     try-name: other
     treeherder-symbol: T(o)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 1500
     mozharness:
         extra-options:
             - --suite=other
-            - --add-option
-            - --webServer,localhost
 
 talos-other-profiling:
     description: "Talos profiling other"
     try-name: other-profiling
     treeherder-symbol: T-P(o)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1500
     mozharness:
         extra-options:
             - --suite=other
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-perf-reftest:
     description: "Talos perf-reftest"
     try-name: perf-reftest
     treeherder-symbol: T(p)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
@@ -405,115 +363,99 @@ talos-speedometer:
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 1500
     mozharness:
         extra-options:
             - --suite=speedometer
-            - --add-option
-            - --webServer,localhost
 
 talos-speedometer-profiling:
     description: "Talos profiling speedometer"
     try-name: speedometer-profiling
     treeherder-symbol: T-P(sp)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1500
     mozharness:
         extra-options:
             - --suite=speedometer
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-svgr:
     description: "Talos svgr"
     try-name: svgr
     treeherder-symbol: T(s)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 1800
     mozharness:
         extra-options:
             - --suite=svgr
-            - --add-option
-            - --webServer,localhost
 
 talos-svgr-profiling:
     description: "Talos profiling svgr"
     try-name: svgr-profiling
     treeherder-symbol: T-P(s)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1800
     mozharness:
         extra-options:
             - --suite=svgr
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-tp5o:
     description: "Talos tp5o"
     try-name: tp5o
     treeherder-symbol: T(tp)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 1800
     mozharness:
         extra-options:
             - --suite=tp5o
-            - --add-option
-            - --webServer,localhost
 
 talos-tp5o-profiling:
     description: "Talos profiling tp5o"
     try-name: tp5o-profiling
     treeherder-symbol: T-P(tp)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1800
     mozharness:
         extra-options:
             - --suite=tp5o
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-tp6:
     description: "Talos tp6"
     try-name: tp6
     treeherder-symbol: T(tp6)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     max-run-time: 1200
     mozharness:
         extra-options:
             - --suite=tp6
-            - --add-option
-            - --webServer,localhost
 
 talos-tp6-profiling:
     description: "Talos profiling tp6"
     try-name: tp6-profiling
     treeherder-symbol: T-P(tp6)
     run-on-projects: ['mozilla-central', 'try']
     max-run-time: 1200
     mozharness:
         extra-options:
             - --suite=tp6
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-tp6-stylo-threads:
     description: "Talos Stylo sequential tp6"
     try-name: tp6-stylo-threads
     treeherder-symbol: Tss(tp6)
     max-run-time: 1200
     run-on-projects:
         by-test-platform:
@@ -532,43 +474,37 @@ talos-tps:
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             linux64-ccov/.*: ['try']  # Bug 1407593
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     mozharness:
         extra-options:
             - --suite=tps
-            - --add-option
-            - --webServer,localhost
 
 talos-tps-profiling:
     description: "Talos page scroll profiling (tps)"
     try-name: tps-profiling
     treeherder-symbol: T-P(tps)
     max-run-time: 900
     run-on-projects: ['mozilla-central', 'try']
     mozharness:
         extra-options:
             - --suite=tps
             - --geckoProfile
-            - --add-option
-            - --webServer,localhost
 
 talos-xperf:
     description: "Talos xperf"
     try-name: xperf
     treeherder-symbol: T(x)
     virtualization: virtual
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             windows7-32.*: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
             default: []
     mozharness:
         extra-options:
             - --suite=xperf
-            - --add-option
-            - --webServer,localhost
         config:
             by-test-platform:
                 windows.*:
                     - talos/windows_vm_config.py
--- a/taskcluster/taskgraph/actions/registry.py
+++ b/taskcluster/taskgraph/actions/registry.py
@@ -9,17 +9,17 @@ from __future__ import absolute_import, 
 import json
 import os
 import re
 import yaml
 from slugid import nice as slugid
 from types import FunctionType
 from collections import namedtuple
 from taskgraph import create, GECKO
-from taskgraph.generator import load_graph_config
+from taskgraph.config import load_graph_config
 from taskgraph.util import taskcluster
 from taskgraph.parameters import Parameters
 
 
 actions = []
 callbacks = {}
 
 Action = namedtuple('Action', [
--- a/taskcluster/taskgraph/actions/release_promotion.py
+++ b/taskcluster/taskgraph/actions/release_promotion.py
@@ -349,9 +349,9 @@ def release_promotion_action(parameters,
     parameters['release_enable_emefree'] = release_enable_emefree
 
     if input['version']:
         parameters['version'] = input['version']
 
     # make parameters read-only
     parameters = Parameters(**parameters)
 
-    taskgraph_decision({}, parameters=parameters)
+    taskgraph_decision({'root': graph_config.root_dir}, parameters=parameters)
--- a/taskcluster/taskgraph/config.py
+++ b/taskcluster/taskgraph/config.py
@@ -1,17 +1,24 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
+import os
+import logging
+import attr
+import yaml
+
 from .util.schema import validate_schema, Schema
 from voluptuous import Required
 
+logger = logging.getLogger(__name__)
+
 graph_config_schema = Schema({
     # The trust-domain for this graph.
     # (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain)  # noqa
     Required('trust-domain'): basestring,
     # This specifes the prefix for repo parameters that refer to the project being built.
     # This selects between `head_rev` and `comm_head_rev` and related paramters.
     # (See http://firefox-source-docs.mozilla.org/taskcluster/taskcluster/parameters.html#push-information  # noqa
     # and http://firefox-source-docs.mozilla.org/taskcluster/taskcluster/parameters.html#comm-push-information)  # noqa
@@ -25,19 +32,44 @@ graph_config_schema = Schema({
     },
     Required('try'): {
         # We have a few platforms for which we want to do some "extra" builds, or at
         # least build-ish things.  Sort of.  Anyway, these other things are implemented
         # as different "platforms".  These do *not* automatically ride along with "-p
         # all"
         Required('ridealong-builds', default={}): {basestring: [basestring]},
     },
+    Required('release-promotion'): {
+        Required('products'): [basestring],
+    },
     Required('scriptworker'): {
         # Prefix to add to scopes controlling scriptworkers
         Required('scope-prefix'): basestring,
         # Mapping of scriptworker types to scopes they accept
         Required('worker-types'): {basestring: [basestring]}
     },
 })
 
 
+@attr.s(frozen=True)
+class GraphConfig(object):
+    _config = attr.ib()
+    root_dir = attr.ib()
+
+    def __getitem__(self, name):
+        return self._config[name]
+
+
 def validate_graph_config(config):
     return validate_schema(graph_config_schema, config, "Invalid graph configuration:")
+
+
+def load_graph_config(root_dir):
+    config_yml = os.path.join(root_dir, "config.yml")
+    if not os.path.exists(config_yml):
+        raise Exception("Couldn't find taskgraph configuration: {}".format(config_yml))
+
+    logger.debug("loading config from `{}`".format(config_yml))
+    with open(config_yml) as f:
+        config = yaml.load(f)
+
+    validate_graph_config(config)
+    return GraphConfig(config=config, root_dir=root_dir)
--- a/taskcluster/taskgraph/generator.py
+++ b/taskcluster/taskgraph/generator.py
@@ -15,17 +15,17 @@ from .task import Task
 from .optimize import optimize_task_graph
 from .morph import morph
 from .util.python_path import find_object
 from .transforms.base import TransformSequence, TransformConfig
 from .util.verify import (
     verify_docs,
     verifications,
 )
-from .config import validate_graph_config
+from .config import load_graph_config
 
 logger = logging.getLogger(__name__)
 
 
 class KindNotFound(Exception):
     """
     Raised when trying to load kind from a directory without a kind.yml.
     """
@@ -82,29 +82,16 @@ class Kind(object):
 
         logger.debug("loading kind `{}` from `{}`".format(kind_name, path))
         with open(kind_yml) as f:
             config = yaml.load(f)
 
         return cls(kind_name, path, config, graph_config)
 
 
-def load_graph_config(root_dir):
-    config_yml = os.path.join(root_dir, "config.yml")
-    if not os.path.exists(config_yml):
-        raise Exception("Couldn't find taskgraph configuration: {}".format(config_yml))
-
-    logger.debug("loading config from `{}`".format(config_yml))
-    with open(config_yml) as f:
-        config = yaml.load(f)
-
-    validate_graph_config(config)
-    return config
-
-
 class TaskGraphGenerator(object):
     """
     The central controller for taskgraph.  This handles all phases of graph
     generation.  The task is generated from all of the kinds defined in
     subdirectories of the generator's root directory.
 
     Access to the results of this generation, as well as intermediate values at
     various phases of generation, is available via properties.  This encourages
--- a/taskcluster/taskgraph/transforms/job/mozharness.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness.py
@@ -144,16 +144,17 @@ def mozharness_on_docker_worker_setup(co
                                       extra=run.get('extra-workspace-cache-key'))
     support_vcs_checkout(config, job, taskdesc)
 
     env = worker.setdefault('env', {})
     env.update({
         'MOZHARNESS_CONFIG': ' '.join(run['config']),
         'MOZHARNESS_SCRIPT': run['script'],
         'MH_BRANCH': config.params['project'],
+        'MOZ_SOURCE_CHANGESET': env['GECKO_HEAD_REV'],
         'MH_BUILD_POOL': 'taskcluster',
         'MOZ_BUILD_DATE': config.params['moz_build_date'],
         'MOZ_SCM_LEVEL': config.params['level'],
         'MOZ_AUTOMATION': '1',
     })
 
     if 'actions' in run:
         env['MOZHARNESS_ACTIONS'] = ' '.join(run['actions'])
@@ -240,16 +241,18 @@ def mozharness_on_generic_worker(config,
 
     docker_worker_add_gecko_vcs_env_vars(config, job, taskdesc)
 
     env = worker['env']
     env.update({
         'MOZ_BUILD_DATE': config.params['moz_build_date'],
         'MOZ_SCM_LEVEL': config.params['level'],
         'MOZ_AUTOMATION': '1',
+        'MH_BRANCH': config.params['project'],
+        'MOZ_SOURCE_CHANGESET': env['GECKO_HEAD_REV'],
     })
     if run['use-simple-package']:
         env.update({'MOZ_SIMPLE_PACKAGE_NAME': 'target'})
 
     if 'extra-config' in run:
         env['EXTRA_MOZHARNESS_CONFIG'] = json.dumps(run['extra-config'])
 
     # The windows generic worker uses batch files to pass environment variables
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -164,19 +164,17 @@ task_description_schema = Schema({
         'push',
         'ship',
     ),
 
     # The `shipping_product` attribute, defaulting to None. This specifies the
     # release promotion product that this task belongs to.
     Required('shipping-product'): Any(
         None,
-        'devedition',
-        'fennec',
-        'firefox',
+        basestring
     ),
 
     # Coalescing provides the facility for tasks to be superseded by the same
     # task in a subsequent commit, if the current task backlog reaches an
     # explicit threshold. Both age and size thresholds need to be met in order
     # for coalescing to be triggered.
     Optional('coalesce'): {
         # A unique identifier per job (typically a hash of the job label) in
@@ -673,26 +671,26 @@ def superseder_url(config, task):
     size = task['coalesce']['size']
     return SUPERSEDER_URL.format(
         age=age,
         size=size,
         key=key
     )
 
 
-UNSUPPORTED_PRODUCT_ERROR = """\
+UNSUPPORTED_INDEX_PRODUCT_ERROR = """\
 The gecko-v2 product {product} is not in the list of configured products in
 `taskcluster/ci/config.yml'.
 """
 
 
 def verify_index(config, index):
     product = index['product']
     if product not in config.graph_config['index']['products']:
-        raise Exception(UNSUPPORTED_PRODUCT_ERROR.format(product=product))
+        raise Exception(UNSUPPORTED_INDEX_PRODUCT_ERROR.format(product=product))
 
 
 @payload_builder('docker-worker')
 def build_docker_worker_payload(config, task, task_def):
     worker = task['worker']
     level = int(config.params['level'])
 
     image = worker['docker-image']
@@ -1235,22 +1233,35 @@ def task_name_from_label(config, tasks):
             if 'name' not in task:
                 raise Exception("task has neither a name nor a label")
             task['label'] = '{}-{}'.format(config.kind, task['name'])
         if task.get('name'):
             del task['name']
         yield task
 
 
+UNSUPPORTED_SHIPPING_PRODUCT_ERROR = """\
+The shipping product {product} is not in the list of configured products in
+`taskcluster/ci/config.yml'.
+"""
+
+
+def validate_shipping_product(config, product):
+    if product not in config.graph_config['release-promotion']['products']:
+        raise Exception(UNSUPPORTED_SHIPPING_PRODUCT_ERROR.format(product=product))
+
+
 @transforms.add
 def validate(config, tasks):
     for task in tasks:
         validate_schema(
             task_description_schema, task,
             "In task {!r}:".format(task.get('label', '?no-label?')))
+        if task['shipping-product'] is not None:
+            validate_shipping_product(config, task['shipping-product'])
         yield task
 
 
 @index_builder('generic')
 def add_generic_index_routes(config, task):
     index = task.get('index')
     routes = task.setdefault('routes', [])
 
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -468,19 +468,21 @@ def validate(config, tests):
 def setup_talos(config, tests):
     """Add options that are specific to talos jobs (identified by suite=talos)"""
     for test in tests:
         if test['suite'] != 'talos':
             yield test
             continue
 
         extra_options = test.setdefault('mozharness', {}).setdefault('extra-options', [])
-        extra_options.append('--add-option')
-        extra_options.append('--webServer,localhost')
         extra_options.append('--use-talos-json')
+        # win7 needs to test skip
+        if test['build-platform'].startswith('win32'):
+            extra_options.append('--add-option')
+            extra_options.append('--setpref,gfx.direct2d.disabled=true')
 
         # Per https://bugzilla.mozilla.org/show_bug.cgi?id=1357753#c3, branch
         # name is only required for try
         if config.params.is_try():
             extra_options.append('--branch-name')
             extra_options.append('try')
 
         yield test
--- a/testing/mochitest/tests/SimpleTest/WindowSnapshot.js
+++ b/testing/mochitest/tests/SimpleTest/WindowSnapshot.js
@@ -16,17 +16,17 @@ function snapshotRect(win, rect) {
   return SpecialPowers.snapshotRect(win, rect);
 }
 
 // If the two snapshots don't compare as expected (true for equal, false for
 // unequal), returns their serializations as data URIs.  In all cases, returns
 // whether the comparison was as expected.
 function compareSnapshots(s1, s2, expectEqual, fuzz) {
   if (s1.width != s2.width || s1.height != s2.height) {
-    ok(false, "Snapshot canvases are not the same size - comparing them makes no sense");
+    ok(false, "Snapshot canvases are not the same size: " + s1.width + "x" + s1.height + " vs. " + s2.width + "x" + s2.height);
     return [false];
   }
   var passed = false;
   var numDifferentPixels;
   var maxDifference = { value: undefined };
   if (gWindowUtils) {
     var equal;
     try {
--- a/testing/mozharness/mozharness/mozilla/testing/talos.py
+++ b/testing/mozharness/mozharness/mozilla/testing/talos.py
@@ -471,17 +471,19 @@ class Talos(TestingMixin, MercurialScrip
         if 'win' in os_name:
             # setup python 3.x virtualenv
             self.setup_py3_virtualenv()
 
         # install mitmproxy
         self.install_mitmproxy()
 
         # download the recording set; will be overridden by the --no-download
-        if '--no-download' not in self.config['talos_extra_options']:
+        if ('talos_extra_options' in self.config and \
+            '--no-download' not in self.config['talos_extra_options']) or \
+            'talos_extra_options' not in self.config:
             self.download_mitmproxy_recording_set()
         else:
             self.info("Not downloading mitmproxy recording set because no-download was specified")
 
     def setup_py3_virtualenv(self):
         """Mitmproxy needs Python 3.x; set up a separate py 3.x env here"""
         self.info("Setting up python 3.x virtualenv, required for mitmproxy")
         # first download the py3 package
@@ -502,17 +504,19 @@ class Talos(TestingMixin, MercurialScrip
             self.py3_install_modules(modules=['mitmproxy'])
             self.mitmdump = os.path.join(self.py3_path_to_executables(), 'mitmdump')
         else:
             # on macosx and linux64 we use a prebuilt mitmproxy release binary
             mitmproxy_path = os.path.join(self.talos_path, 'talos', 'mitmproxy')
             self.mitmdump = os.path.join(mitmproxy_path, 'mitmdump')
             if not os.path.exists(self.mitmdump):
                 # download the mitmproxy release binary; will be overridden by the --no-download
-                if '--no-download' not in self.config['talos_extra_options']:
+                if ('talos_extra_options' in self.config and \
+                   '--no-download' not in self.config['talos_extra_options']) or \
+                   'talos_extra_options' not in self.config:
                     if 'osx' in self.platform_name():
                         _platform = 'osx'
                     else:
                         _platform = 'linux64'
                     self.query_mitmproxy_rel_bin(_platform)
                     if self.mitmproxy_rel_bin is None:
                         self.fatal("Aborting: mitmproxy_release_bin_osx not found in talos.json")
                     self.download_mitmproxy_binary(_platform)
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -343019,16 +343019,26 @@
     ]
    ],
    "html/webappapis/timers/evil-spec-example.html": [
     [
      "/html/webappapis/timers/evil-spec-example.html",
      {}
     ]
    ],
+   "html/webappapis/timers/missing-timeout-setinterval.any.js": [
+    [
+     "/html/webappapis/timers/missing-timeout-setinterval.any.html",
+     {}
+    ],
+    [
+     "/html/webappapis/timers/missing-timeout-setinterval.any.worker.html",
+     {}
+    ]
+   ],
    "html/webappapis/timers/negative-setinterval.html": [
     [
      "/html/webappapis/timers/negative-setinterval.html",
      {}
     ]
    ],
    "html/webappapis/timers/negative-settimeout.html": [
     [
@@ -579708,16 +579718,20 @@
   "html/webappapis/timers/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
   "html/webappapis/timers/evil-spec-example.html": [
    "49fd55dbbf64c6973a0e76284c0e3d8b7bf0ef3c",
    "testharness"
   ],
+  "html/webappapis/timers/missing-timeout-setinterval.any.js": [
+   "79b4a278f0e35646cfdffeebf8f0523e2772bc9b",
+   "testharness"
+  ],
   "html/webappapis/timers/negative-setinterval.html": [
    "405046cab9cd15a88d57eace1f293ebdd7b1b3e2",
    "testharness"
   ],
   "html/webappapis/timers/negative-settimeout.html": [
    "e5673e7cca2b006afd3e2e4e5dd3e56fb10efa4e",
    "testharness"
   ],
--- a/testing/web-platform/meta/dom/events/relatedTarget.window.js.ini
+++ b/testing/web-platform/meta/dom/events/relatedTarget.window.js.ini
@@ -1,4 +1,5 @@
 [relatedTarget.window.html]
-  [Untitled]
+  prefs: [dom.webcomponents.shadowdom.enabled:true]
+  [Reset targets before activation behavior]
     expected: FAIL
 
--- a/testing/web-platform/tests/dom/events/relatedTarget.window.js
+++ b/testing/web-platform/tests/dom/events/relatedTarget.window.js
@@ -36,23 +36,25 @@ async_test(t => {
   assert_equals(event.target, null);
   assert_equals(event.relatedTarget, null);
   shadowChild.remove();
   t.done();
 }, "Reset if target pointed to a shadow tree pre-dispatch");
 
 async_test(t => {
   const shadowChild = shadow.appendChild(document.createElement("div"));
-  shadowChild.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+  const shadowChild2 = shadow.appendChild(document.createElement("div"));
+  shadowChild2.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
   const event = new FocusEvent("demo", { relatedTarget: shadowChild });
-  document.body.dispatchEvent(event);
+  shadowChild2.dispatchEvent(event);
   assert_equals(shadowChild.parentNode, document.body);
   assert_equals(event.target, null);
   assert_equals(event.relatedTarget, null);
   shadowChild.remove();
+  shadowChild2.remove();
   t.done();
 }, "Reset if relatedTarget pointed to a shadow tree pre-dispatch");
 
 async_test(t => {
   const event = new FocusEvent("heya", { relatedTarget: shadow, cancelable: true }),
         callback = t.unreached_func();
   host.addEventListener("heya", callback);
   t.add_cleanup(() => host.removeEventListener("heya", callback));
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js
@@ -0,0 +1,34 @@
+function timeout_trampoline(t, timeout, message) {
+  t.step_timeout(function() {
+    // Yield in case we managed to be called before the second interval callback.
+    t.step_timeout(function() {
+      assert_unreached(message);
+    }, timeout);
+  }, timeout);
+}
+
+async_test(function(t) {
+  let ctr = 0;
+  let h = setInterval(t.step_func(function() {
+    if (++ctr == 2) {
+      clearInterval(h);
+      t.done();
+      return;
+    }
+  }) /* no interval */);
+
+  timeout_trampoline(t, 100, "Expected setInterval callback to be called two times");
+}, "Calling setInterval with no interval should be the same as if called with 0 interval");
+
+async_test(function(t) {
+  let ctr = 0;
+  let h = setInterval(t.step_func(function() {
+    if (++ctr == 2) {
+      clearInterval(h);
+      t.done();
+      return;
+    }
+  }),  undefined);
+
+  timeout_trampoline(t, 100, "Expected setInterval callback to be called two times");
+}, "Calling setInterval with undefined interval should be the same as if called with 0 interval");
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/.coveragerc
@@ -0,0 +1,13 @@
+[run]
+branch = True
+source =
+    attr
+
+[paths]
+source =
+   src/attr
+   .tox/*/lib/python*/site-packages/attr
+   .tox/pypy/site-packages/attr
+
+[report]
+show_missing = True
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/.github/CODE_OF_CONDUCT.rst
@@ -0,0 +1,55 @@
+Contributor Covenant Code of Conduct
+====================================
+
+Our Pledge
+----------
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+Our Standards
+-------------
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+Our Responsibilities
+--------------------
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+Scope
+-----
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
+Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+Representation of a project may be further defined and clarified by project maintainers.
+
+Enforcement
+-----------
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hs@ox.cx.
+All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
+The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+Attribution
+-----------
+
+This Code of Conduct is adapted from the `Contributor Covenant <https://www.contributor-covenant.org>`_, version 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/.github/CONTRIBUTING.rst
@@ -0,0 +1,220 @@
+How To Contribute
+=================
+
+First off, thank you for considering contributing to ``attrs``!
+It's people like *you* who make it such a great tool for everyone.
+
+This document intends to make contribution more accessible by codifying tribal knowledge and expectations.
+Don't be afraid to open half-finished PRs, and ask questions if something is unclear!
+
+
+Support
+-------
+
+In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity:
+help your fellow developers on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_!
+
+The offical tag is ``python-attrs`` and helping out in support frees us up to improve ``attrs`` instead!
+
+
+Workflow
+--------
+
+- No contribution is too small!
+  Please submit as many fixes for typos and grammar bloopers as you can!
+- Try to limit each pull request to *one* change only.
+- *Always* add tests and docs for your code.
+  This is a hard rule; patches with missing tests or documentation can't be merged.
+- Make sure your changes pass our CI_.
+  You won't get any feedback until it's green unless you ask for it.
+- Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done.
+- Don’t break `backward compatibility`_.
+
+
+Code
+----
+
+- Obey `PEP 8`_ and `PEP 257`_.
+  We use the ``"""``\ -on-separate-lines style for docstrings:
+
+  .. code-block:: python
+
+     def func(x):
+         """
+         Do something.
+
+         :param str x: A very important parameter.
+
+         :rtype: str
+         """
+- If you add or change public APIs, tag the docstring using ``..  versionadded:: 16.0.0 WHAT`` or ``..  versionchanged:: 16.2.0 WHAT``.
+- Prefer double quotes (``"``) over single quotes (``'``) unless the string contains double quotes itself.
+
+
+Tests
+-----
+
+- Write your asserts as ``expected == actual`` to line them up nicely:
+
+  .. code-block:: python
+
+     x = f()
+
+     assert 42 == x.some_attribute
+     assert "foo" == x._a_private_attribute
+
+- To run the test suite, all you need is a recent tox_.
+  It will ensure the test suite runs with all dependencies against all Python versions just as it will on Travis CI.
+  If you lack some Python versions, you can can always limit the environments like ``tox -e py27,py35`` (in that case you may want to look into pyenv_, which makes it very easy to install many different Python versions in parallel).
+- Write `good test docstrings`_.
+- To ensure new features work well with the rest of the system, they should be also added to our `Hypothesis`_ testing strategy, which is found in ``tests/strategies.py``.
+
+
+Documentation
+-------------
+
+- Use `semantic newlines`_ in reStructuredText_ files (files ending in ``.rst``):
+
+  .. code-block:: rst
+
+     This is a sentence.
+     This is another sentence.
+
+- If you start a new section, add two blank lines before and one blank line after the header, except if two headers follow immediately after each other:
+
+  .. code-block:: rst
+
+     Last line of previous section.
+
+
+     Header of New Top Section
+     -------------------------
+
+     Header of New Section
+     ^^^^^^^^^^^^^^^^^^^^^
+
+     First line of new section.
+
+- If you add a new feature, demonstrate its awesomeness on the `examples page`_!
+
+
+Changelog
+^^^^^^^^^
+
+If your change is noteworthy, there needs to be a changelog entry so our users can learn about it!
+
+To avoid merge conflicts, we use the towncrier_ package to manage our changelog.
+``towncrier`` uses independent files for each pull request -- so called *news fragments* -- instead of one monolithic changelog file.
+On release, those news fragments are compiled into our ``CHANGELOG.rst``.
+
+You don't need to install ``towncrier`` yourself, you just have to abide by a few simple rules:
+
+- For each pull request, add a new file into ``changelog.d`` with a filename adhering to the ``pr#.(change|deprecation|breaking).rst`` schema:
+  For example, ``changelog.d/42.change.rst`` for a non-breaking change that is proposed in pull request #42.
+- As with other docs, please use `semantic newlines`_ within news fragments.
+- Wrap symbols like modules, functions, or classes into double backticks so they are rendered in a monospace font.
+- If you mention functions or other callables, add parentheses at the end of their names: ``attr.func()`` or ``attr.Class.method()``.
+  This makes the changelog a lot more readable.
+- Prefer simple past tense or constructions with "now".
+  For example:
+
+  + Added ``attr.validators.func()``.
+  + ``attr.func()`` now doesn't crash the Large Hadron Collider anymore.
+- If you want to reference multiple issues, copy the news fragment to another filename.
+  ``towncrier`` will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests.
+
+Example entries:
+
+  .. code-block:: rst
+
+     Added ``attr.validators.func()``.
+     The feature really *is* awesome.
+
+or:
+
+  .. code-block:: rst
+
+     ``attr.func()`` now doesn't crash the Large Hadron Collider anymore.
+     The bug really *was* nasty.
+
+----
+
+``tox -e changelog`` will render the current changelog to the terminal if you have any doubts.
+
+
+Local Development Environment
+-----------------------------
+
+You can (and should) run our test suite using tox_.
+However, you’ll probably want a more traditional environment as well.
+We highly recommend to develop using the latest Python 3 release because ``attrs`` tries to take advantage of modern features whenever possible.
+
+First create a `virtual environment <https://virtualenv.pypa.io/>`_.
+It’s out of scope for this document to list all the ways to manage virtual environments in Python, but if you don’t already have a pet way, take some time to look at tools like `pew <https://github.com/berdario/pew>`_, `virtualfish <http://virtualfish.readthedocs.io/>`_, and `virtualenvwrapper <http://virtualenvwrapper.readthedocs.io/>`_.
+
+Next, get an up to date checkout of the ``attrs`` repository:
+
+.. code-block:: bash
+
+    $ git checkout git@github.com:python-attrs/attrs.git
+
+Change into the newly created directory and **after activating your virtual environment** install an editable version of ``attrs`` along with its tests and docs requirements:
+
+.. code-block:: bash
+
+    $ cd attrs
+    $ pip install -e .[dev]
+
+At this point,
+
+.. code-block:: bash
+
+   $ python -m pytest
+
+should work and pass, as should:
+
+.. code-block:: bash
+
+   $ cd docs
+   $ make html
+
+The built documentation can then be found in ``docs/_build/html/``.
+
+
+Governance
+----------
+
+``attrs`` is maintained by `team of volunteers`_ that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort.
+If you'd like to join, just get a pull request merged and ask to be added in the very same pull request!
+
+**The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.**
+
+`Hynek Schlawack`_ acts reluctantly as the BDFL_ and has the final say over design decisions.
+
+
+****
+
+Please note that this project is released with a Contributor `Code of Conduct`_.
+By participating in this project you agree to abide by its terms.
+Please report any harm to `Hynek Schlawack`_ in any way you find appropriate.
+
+Thank you for considering contributing to ``attrs``!
+
+
+.. _`Hynek Schlawack`: https://hynek.me/about/
+.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
+.. _`PEP 257`: https://www.python.org/dev/peps/pep-0257/
+.. _`good test docstrings`: https://jml.io/pages/test-docstrings.html
+.. _`Code of Conduct`: https://github.com/python-attrs/attrs/blob/master/.github/CODE_OF_CONDUCT.rst
+.. _changelog: https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst
+.. _`backward compatibility`: http://www.attrs.org/en/latest/backward-compatibility.html
+.. _tox: https://tox.readthedocs.io/
+.. _pyenv: https://github.com/pyenv/pyenv
+.. _reStructuredText: http://www.sphinx-doc.org/en/stable/rest.html
+.. _semantic newlines: http://rhodesmill.org/brandon/2012/one-sentence-per-line/
+.. _examples page: https://github.com/python-attrs/attrs/blob/master/docs/examples.rst
+.. _Hypothesis: https://hypothesis.readthedocs.io/
+.. _CI: https://travis-ci.org/python-attrs/attrs/
+.. _`team of volunteers`: https://github.com/python-attrs
+.. _BDFL: https://en.wikipedia.org/wiki/Benevolent_dictator_for_life
+.. _towncrier: https://pypi.org/project/towncrier
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/.readthedocs.yml
@@ -0,0 +1,6 @@
+---
+python:
+  version: 3
+  pip_install: true
+  extra_requirements:
+    - docs
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/AUTHORS.rst
@@ -0,0 +1,11 @@
+Credits
+=======
+
+``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
+
+The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
+
+A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
+
+It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
+Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `sub-classing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/CHANGELOG.rst
@@ -0,0 +1,471 @@
+Changelog
+=========
+
+Versions follow `CalVer <http://calver.org>`_ with a strict backwards compatibility policy.
+The third digit is only for regressions.
+
+.. towncrier release notes start
+
+18.1.0 (2018-05-03)
+-------------------
+
+Changes
+^^^^^^^
+
+- ``x=X(); x.cycle = x; repr(x)`` will no longer raise a ``RecursionError``, and will instead show as ``X(x=...)``.
+
+  `#95 <https://github.com/python-attrs/attrs/issues/95>`_
+- ``attr.ib(factory=f)`` is now syntactic sugar for the common case of ``attr.ib(default=attr.Factory(f))``.
+
+  `#178 <https://github.com/python-attrs/attrs/issues/178>`_,
+  `#356 <https://github.com/python-attrs/attrs/issues/356>`_
+- Added ``attr.field_dict()`` to return an ordered dictionary of ``attrs`` attributes for a class, whose keys are the attribute names.
+
+  `#290 <https://github.com/python-attrs/attrs/issues/290>`_,
+  `#349 <https://github.com/python-attrs/attrs/issues/349>`_
+- The order of attributes that are passed into ``attr.make_class()`` or the ``these`` argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise).
+
+  Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically.
+
+  `#300 <https://github.com/python-attrs/attrs/issues/300>`_,
+  `#339 <https://github.com/python-attrs/attrs/issues/339>`_,
+  `#343 <https://github.com/python-attrs/attrs/issues/343>`_
+- In slotted classes, ``__getstate__`` and ``__setstate__`` now ignore the ``__weakref__`` attribute.
+
+  `#311 <https://github.com/python-attrs/attrs/issues/311>`_,
+  `#326 <https://github.com/python-attrs/attrs/issues/326>`_
+- Setting the cell type is now completely best effort.
+  This fixes ``attrs`` on Jython.
+
+  We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatabilities.
+
+  `#321 <https://github.com/python-attrs/attrs/issues/321>`_,
+  `#334 <https://github.com/python-attrs/attrs/issues/334>`_
+- If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.
+
+  `#322 <https://github.com/python-attrs/attrs/issues/322>`_,
+  `#323 <https://github.com/python-attrs/attrs/issues/323>`_
+- The hash of ``attr.NOTHING`` is now vegan and faster on 32bit Python builds.
+
+  `#331 <https://github.com/python-attrs/attrs/issues/331>`_,
+  `#332 <https://github.com/python-attrs/attrs/issues/332>`_
+- The overhead of instantiating frozen dict classes is virtually eliminated.
+  `#336 <https://github.com/python-attrs/attrs/issues/336>`_
+- Generated ``__init__`` methods now have an ``__annotations__`` attribute derived from the types of the fields.
+
+  `#363 <https://github.com/python-attrs/attrs/issues/363>`_
+- We have restructured the documentation a bit to account for ``attrs``' growth in scope.
+  Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.
+
+  So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.
+
+  Expect more to come!
+
+  `#369 <https://github.com/python-attrs/attrs/issues/369>`_,
+  `#370 <https://github.com/python-attrs/attrs/issues/370>`_
+
+
+----
+
+
+17.4.0 (2017-12-30)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- The traversal of MROs when using multiple inheritance was backward:
+  If you defined a class ``C`` that subclasses ``A`` and ``B`` like ``C(A, B)``, ``attrs`` would have collected the attributes from ``B`` *before* those of ``A``.
+
+  This is now fixed and means that in classes that employ multiple inheritance, the output of ``__repr__`` and the order of positional arguments in ``__init__`` changes.
+  Due to the nature of this bug, a proper deprecation cycle was unfortunately impossible.
+
+  Generally speaking, it's advisable to prefer ``kwargs``-based initialization anyways – *especially* if you employ multiple inheritance and diamond-shaped hierarchies.
+
+  `#298 <https://github.com/python-attrs/attrs/issues/298>`_,
+  `#299 <https://github.com/python-attrs/attrs/issues/299>`_,
+  `#304 <https://github.com/python-attrs/attrs/issues/304>`_
+- The ``__repr__`` set by ``attrs``
+  no longer produces an ``AttributeError``
+  when the instance is missing some of the specified attributes
+  (either through deleting
+  or after using ``init=False`` on some attributes).
+
+  This can break code
+  that relied on ``repr(attr_cls_instance)`` raising ``AttributeError``
+  to check if any attr-specified members were unset.
+
+  If you were using this,
+  you can implement a custom method for checking this::
+
+      def has_unset_members(self):
+          for field in attr.fields(type(self)):
+              try:
+                  getattr(self, field.name)
+              except AttributeError:
+                  return True
+          return False
+
+  `#308 <https://github.com/python-attrs/attrs/issues/308>`_
+
+
+Deprecations
+^^^^^^^^^^^^
+
+- The ``attr.ib(convert=callable)`` option is now deprecated in favor of ``attr.ib(converter=callable)``.
+
+  This is done to achieve consistency with other noun-based arguments like *validator*.
+
+  *convert* will keep working until at least January 2019 while raising a ``DeprecationWarning``.
+
+  `#307 <https://github.com/python-attrs/attrs/issues/307>`_
+
+
+Changes
+^^^^^^^
+
+- Generated ``__hash__`` methods now hash the class type along with the attribute values.
+  Until now the hashes of two classes with the same values were identical which was a bug.
+
+  The generated method is also *much* faster now.
+
+  `#261 <https://github.com/python-attrs/attrs/issues/261>`_,
+  `#295 <https://github.com/python-attrs/attrs/issues/295>`_,
+  `#296 <https://github.com/python-attrs/attrs/issues/296>`_
+- ``attr.ib``\ ’s ``metadata`` argument now defaults to a unique empty ``dict`` instance instead of sharing a common empty ``dict`` for all.
+  The singleton empty ``dict`` is still enforced.
+
+  `#280 <https://github.com/python-attrs/attrs/issues/280>`_
+- ``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slotted classes.
+  This should only happen in special environments like Google App Engine.
+
+  `#284 <https://github.com/python-attrs/attrs/issues/284>`_,
+  `#286 <https://github.com/python-attrs/attrs/issues/286>`_
+- The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance.
+  In that case, the definition that is closer to the base of the class hierarchy wins.
+
+  `#285 <https://github.com/python-attrs/attrs/issues/285>`_,
+  `#287 <https://github.com/python-attrs/attrs/issues/287>`_
+- Subclasses of ``auto_attribs=True`` can be empty now.
+
+  `#291 <https://github.com/python-attrs/attrs/issues/291>`_,
+  `#292 <https://github.com/python-attrs/attrs/issues/292>`_
+- Equality tests are *much* faster now.
+
+  `#306 <https://github.com/python-attrs/attrs/issues/306>`_
+- All generated methods now have correct ``__module__``, ``__name__``, and (on Python 3) ``__qualname__`` attributes.
+
+  `#309 <https://github.com/python-attrs/attrs/issues/309>`_
+
+
+----
+
+
+17.3.0 (2017-11-08)
+-------------------
+
+Backward-incompatible Changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Attributes are not defined on the class body anymore.
+
+  This means that if you define a class ``C`` with an attribute ``x``, the class will *not* have an attribute ``x`` for introspection anymore.
+  Instead of ``C.x``, use ``attr.fields(C).x`` or look at ``C.__attrs_attrs__``.
+  The old behavior has been deprecated since version 16.1.
+  (`#253 <https://github.com/python-attrs/attrs/issues/253>`_)
+
+
+Changes
+^^^^^^^
+
+- ``super()`` and ``__class__`` now work with slotted classes on Python 3.
+  (`#102 <https://github.com/python-attrs/attrs/issues/102>`_, `#226 <https://github.com/python-attrs/attrs/issues/226>`_, `#269 <https://github.com/python-attrs/attrs/issues/269>`_, `#270 <https://github.com/python-attrs/attrs/issues/270>`_, `#272 <https://github.com/python-attrs/attrs/issues/272>`_)
+- Added ``type`` argument to ``attr.ib()`` and corresponding ``type`` attribute to ``attr.Attribute``.
+
+  This change paves the way for automatic type checking and serialization (though as of this release ``attrs`` does not make use of it).
+  In Python 3.6 or higher, the value of ``attr.Attribute.type`` can alternately be set using variable type annotations
+  (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_). (`#151 <https://github.com/python-attrs/attrs/issues/151>`_, `#214 <https://github.com/python-attrs/attrs/issues/214>`_, `#215 <https://github.com/python-attrs/attrs/issues/215>`_, `#239 <https://github.com/python-attrs/attrs/issues/239>`_)
+- The combination of ``str=True`` and ``slots=True`` now works on Python 2.
+  (`#198 <https://github.com/python-attrs/attrs/issues/198>`_)
+- ``attr.Factory`` is hashable again. (`#204
+  <https://github.com/python-attrs/attrs/issues/204>`_)
+- Subclasses now can overwrite attribute definitions of their superclass.
+
+  That means that you can -- for example -- change the default value for an attribute by redefining it.
+  (`#221 <https://github.com/python-attrs/attrs/issues/221>`_, `#229 <https://github.com/python-attrs/attrs/issues/229>`_)
+- Added new option ``auto_attribs`` to ``@attr.s`` that allows to collect annotated fields without setting them to ``attr.ib()``.
+
+  Setting a field to an ``attr.ib()`` is still possible to supply options like validators.
+  Setting it to any other value is treated like it was passed as ``attr.ib(default=value)`` -- passing an instance of ``attr.Factory`` also works as expected.
+  (`#262 <https://github.com/python-attrs/attrs/issues/262>`_, `#277 <https://github.com/python-attrs/attrs/issues/277>`_)
+- Instances of classes created using ``attr.make_class()`` can now be pickled.
+  (`#282 <https://github.com/python-attrs/attrs/issues/282>`_)
+
+
+----
+
+
+17.2.0 (2017-05-24)
+-------------------
+
+
+Changes:
+^^^^^^^^
+
+- Validators are hashable again.
+  Note that validators may become frozen in the future, pending availability of no-overhead frozen classes.
+  `#192 <https://github.com/python-attrs/attrs/issues/192>`_
+
+
+----
+
+
+17.1.0 (2017-05-16)
+-------------------
+
+To encourage more participation, the project has also been moved into a `dedicated GitHub organization <https://github.com/python-attrs/>`_ and everyone is most welcome to join!
+
+``attrs`` also has a logo now!
+
+.. image:: http://www.attrs.org/en/latest/_static/attrs_logo.png
+   :alt: attrs logo
+
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ``attrs`` will set the ``__hash__()`` method to ``None`` by default now.
+  The way hashes were handled before was in conflict with `Python's specification <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_.
+  This *may* break some software although this breakage is most likely just surfacing of latent bugs.
+  You can always make ``attrs`` create the ``__hash__()`` method using ``@attr.s(hash=True)``.
+  See `#136`_ for the rationale of this change.
+
+  .. warning::
+
+    Please *do not* upgrade blindly and *do* test your software!
+    *Especially* if you use instances as dict keys or put them into sets!
+
+- Correspondingly, ``attr.ib``'s ``hash`` argument is ``None`` by default too and mirrors the ``cmp`` argument as it should.
+
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- ``attr.assoc()`` is now deprecated in favor of ``attr.evolve()`` and will stop working in 2018.
+
+
+Changes:
+^^^^^^^^
+
+- Fix default hashing behavior.
+  Now *hash* mirrors the value of *cmp* and classes are unhashable by default.
+  `#136`_
+  `#142 <https://github.com/python-attrs/attrs/issues/142>`_
+- Added ``attr.evolve()`` that, given an instance of an ``attrs`` class and field changes as keyword arguments, will instantiate a copy of the given instance with the changes applied.
+  ``evolve()`` replaces ``assoc()``, which is now deprecated.
+  ``evolve()`` is significantly faster than ``assoc()``, and requires the class have an initializer that can take the field values as keyword arguments (like ``attrs`` itself can generate).
+  `#116 <https://github.com/python-attrs/attrs/issues/116>`_
+  `#124 <https://github.com/python-attrs/attrs/pull/124>`_
+  `#135 <https://github.com/python-attrs/attrs/pull/135>`_
+- ``FrozenInstanceError`` is now raised when trying to delete an attribute from a frozen class.
+  `#118 <https://github.com/python-attrs/attrs/pull/118>`_
+- Frozen-ness of classes is now inherited.
+  `#128 <https://github.com/python-attrs/attrs/pull/128>`_
+- ``__attrs_post_init__()`` is now run if validation is disabled.
+  `#130 <https://github.com/python-attrs/attrs/pull/130>`_
+- Added ``attr.validators.in_(options)`` that, given the allowed `options`, checks whether the attribute value is in it.
+  This can be used to check constants, enums, mappings, etc.
+  `#181 <https://github.com/python-attrs/attrs/pull/181>`_
+- Added ``attr.validators.and_()`` that composes multiple validators into one.
+  `#161 <https://github.com/python-attrs/attrs/issues/161>`_
+- For convenience, the ``validator`` argument of ``@attr.s`` now can take a ``list`` of validators that are wrapped using ``and_()``.
+  `#138 <https://github.com/python-attrs/attrs/issues/138>`_
+- Accordingly, ``attr.validators.optional()`` now can take a ``list`` of validators too.
+  `#161 <https://github.com/python-attrs/attrs/issues/161>`_
+- Validators can now be defined conveniently inline by using the attribute as a decorator.
+  Check out the `examples <http://www.attrs.org/en/stable/examples.html#validators>`_ to see it in action!
+  `#143 <https://github.com/python-attrs/attrs/issues/143>`_
+- ``attr.Factory()`` now has a ``takes_self`` argument that makes the initializer to pass the partially initialized instance into the factory.
+  In other words you can define attribute defaults based on other attributes.
+  `#165`_
+  `#189 <https://github.com/python-attrs/attrs/issues/189>`_
+- Default factories can now also be defined inline using decorators.
+  They are *always* passed the partially initialized instance.
+  `#165`_
+- Conversion can now be made optional using ``attr.converters.optional()``.
+  `#105 <https://github.com/python-attrs/attrs/issues/105>`_
+  `#173 <https://github.com/python-attrs/attrs/pull/173>`_
+- ``attr.make_class()`` now accepts the keyword argument ``bases`` which allows for subclassing.
+  `#152 <https://github.com/python-attrs/attrs/pull/152>`_
+- Metaclasses are now preserved with ``slots=True``.
+  `#155 <https://github.com/python-attrs/attrs/pull/155>`_
+
+.. _`#136`: https://github.com/python-attrs/attrs/issues/136
+.. _`#165`: https://github.com/python-attrs/attrs/issues/165
+
+
+----
+
+
+16.3.0 (2016-11-24)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Attributes now can have user-defined metadata which greatly improves ``attrs``'s extensibility.
+  `#96 <https://github.com/python-attrs/attrs/pull/96>`_
+- Allow for a ``__attrs_post_init__()`` method that -- if defined -- will get called at the end of the ``attrs``-generated ``__init__()`` method.
+  `#111 <https://github.com/python-attrs/attrs/pull/111>`_
+- Added ``@attr.s(str=True)`` that will optionally create a ``__str__()`` method that is identical to ``__repr__()``.
+  This is mainly useful with ``Exception``\ s and other classes that rely on a useful ``__str__()`` implementation but overwrite the default one through a poor own one.
+  Default Python class behavior is to use ``__repr__()`` as ``__str__()`` anyways.
+
+  If you tried using ``attrs`` with ``Exception``\ s and were puzzled by the tracebacks: this option is for you.
+- ``__name__`` is not overwritten with ``__qualname__`` for ``attr.s(slots=True)`` classes anymore.
+  `#99 <https://github.com/python-attrs/attrs/issues/99>`_
+
+
+----
+
+
+16.2.0 (2016-09-17)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added ``attr.astuple()`` that -- similarly to ``attr.asdict()`` -- returns the instance as a tuple.
+  `#77 <https://github.com/python-attrs/attrs/issues/77>`_
+- Converts now work with frozen classes.
+  `#76 <https://github.com/python-attrs/attrs/issues/76>`_
+- Instantiation of ``attrs`` classes with converters is now significantly faster.
+  `#80 <https://github.com/python-attrs/attrs/pull/80>`_
+- Pickling now works with slotted classes.
+  `#81 <https://github.com/python-attrs/attrs/issues/81>`_
+- ``attr.assoc()`` now works with slotted classes.
+  `#84 <https://github.com/python-attrs/attrs/issues/84>`_
+- The tuple returned by ``attr.fields()`` now also allows to access the ``Attribute`` instances by name.
+  Yes, we've subclassed ``tuple`` so you don't have to!
+  Therefore ``attr.fields(C).x`` is equivalent to the deprecated ``C.x`` and works with slotted classes.
+  `#88 <https://github.com/python-attrs/attrs/issues/88>`_
+
+
+----
+
+
+16.1.0 (2016-08-30)
+-------------------
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- All instances where function arguments were called ``cl`` have been changed to the more Pythonic ``cls``.
+  Since it was always the first argument, it's doubtful anyone ever called those function with in the keyword form.
+  If so, sorry for any breakage but there's no practical deprecation path to solve this ugly wart.
+
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- Accessing ``Attribute`` instances on class objects is now deprecated and will stop working in 2017.
+  If you need introspection please use the ``__attrs_attrs__`` attribute or the ``attr.fields()`` function that carry them too.
+  In the future, the attributes that are defined on the class body and are usually overwritten in your ``__init__`` method are simply removed after ``@attr.s`` has been applied.
+
+  This will remove the confusing error message if you write your own ``__init__`` and forget to initialize some attribute.
+  Instead you will get a straightforward ``AttributeError``.
+  In other words: decorated classes will work more like plain Python classes which was always ``attrs``'s goal.
+- The serious business aliases ``attr.attributes`` and ``attr.attr`` have been deprecated in favor of ``attr.attrs`` and ``attr.attrib`` which are much more consistent and frankly obvious in hindsight.
+  They will be purged from documentation immediately but there are no plans to actually remove them.
+
+
+Changes:
+^^^^^^^^
+
+- ``attr.asdict()``\ 's ``dict_factory`` arguments is now propagated on recursion.
+  `#45 <https://github.com/python-attrs/attrs/issues/45>`_
+- ``attr.asdict()``, ``attr.has()`` and ``attr.fields()`` are significantly faster.
+  `#48 <https://github.com/python-attrs/attrs/issues/48>`_
+  `#51 <https://github.com/python-attrs/attrs/issues/51>`_
+- Add ``attr.attrs`` and ``attr.attrib`` as a more consistent aliases for ``attr.s`` and ``attr.ib``.
+- Add ``frozen`` option to ``attr.s`` that will make instances best-effort immutable.
+  `#60 <https://github.com/python-attrs/attrs/issues/60>`_
+- ``attr.asdict()`` now takes ``retain_collection_types`` as an argument.
+  If ``True``, it does not convert attributes of type ``tuple`` or ``set`` to ``list``.
+  `#69 <https://github.com/python-attrs/attrs/issues/69>`_
+
+
+----
+
+
+16.0.0 (2016-05-23)
+-------------------
+
+Backward-incompatible Changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Python 3.3 and 2.6 aren't supported anymore.
+  They may work by chance but any effort to keep them working has ceased.
+
+  The last Python 2.6 release was on October 29, 2013 and isn't supported by the CPython core team anymore.
+  Major Python packages like Django and Twisted dropped Python 2.6 a while ago already.
+
+  Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release.
+
+Changes:
+^^^^^^^^
+
+- ``__slots__`` have arrived!
+  Classes now can automatically be `slotted <https://docs.python.org/3/reference/datamodel.html#slots>`_-style (and save your precious memory) just by passing ``slots=True``.
+  `#35 <https://github.com/python-attrs/attrs/issues/35>`_
+- Allow the case of initializing attributes that are set to ``init=False``.
+  This allows for clean initializer parameter lists while being able to initialize attributes to default values.
+  `#32 <https://github.com/python-attrs/attrs/issues/32>`_
+- ``attr.asdict()`` can now produce arbitrary mappings instead of Python ``dict``\ s when provided with a ``dict_factory`` argument.
+  `#40 <https://github.com/python-attrs/attrs/issues/40>`_
+- Multiple performance improvements.
+
+
+----
+
+
+15.2.0 (2015-12-08)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added a ``convert`` argument to ``attr.ib``, which allows specifying a function to run on arguments.
+  This allows for simple type conversions, e.g. with ``attr.ib(convert=int)``.
+  `#26 <https://github.com/python-attrs/attrs/issues/26>`_
+- Speed up object creation when attribute validators are used.
+  `#28 <https://github.com/python-attrs/attrs/issues/28>`_
+
+
+----
+
+
+15.1.0 (2015-08-20)
+-------------------
+
+Changes:
+^^^^^^^^
+
+- Added ``attr.validators.optional()`` that wraps other validators allowing attributes to be ``None``.
+  `#16 <https://github.com/python-attrs/attrs/issues/16>`_
+- Multi-level inheritance now works.
+  `#24 <https://github.com/python-attrs/attrs/issues/24>`_
+- ``__repr__()`` now works with non-redecorated subclasses.
+  `#20 <https://github.com/python-attrs/attrs/issues/20>`_
+
+
+----
+
+
+15.0.0 (2015-04-15)
+-------------------
+
+Changes:
+^^^^^^^^
+
+Initial release.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Hynek Schlawack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/MANIFEST.in
@@ -0,0 +1,22 @@
+include LICENSE *.rst *.toml .readthedocs.yml
+
+# Don't package GitHub-specific files.
+exclude .github/*.md .travis.yml
+
+# Tests
+include tox.ini .coveragerc conftest.py
+recursive-include tests *.py
+recursive-include .github *.rst
+
+# Documentation
+include docs/Makefile docs/docutils.conf
+recursive-include docs *.png
+recursive-include docs *.svg
+recursive-include docs *.py
+recursive-include docs *.rst
+prune docs/_build
+
+# Just to keep check-manifest happy; on releases those files are gone.
+# Last rule wins!
+exclude changelog.d/*.rst
+include changelog.d/towncrier_template.rst
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/PKG-INFO
@@ -0,0 +1,240 @@
+Metadata-Version: 2.1
+Name: attrs
+Version: 18.1.0
+Summary: Classes Without Boilerplate
+Home-page: http://www.attrs.org/
+Author: Hynek Schlawack
+Author-email: hs@ox.cx
+Maintainer: Hynek Schlawack
+Maintainer-email: hs@ox.cx
+License: MIT
+Description: .. image:: http://www.attrs.org/en/latest/_static/attrs_logo.png
+           :alt: attrs Logo
+        
+        ======================================
+        ``attrs``: Classes Without Boilerplate
+        ======================================
+        
+        .. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
+           :target: http://www.attrs.org/en/stable/?badge=stable
+           :alt: Documentation Status
+        
+        .. image:: https://travis-ci.org/python-attrs/attrs.svg?branch=master
+           :target: https://travis-ci.org/python-attrs/attrs
+           :alt: CI Status
+        
+        .. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
+           :target: https://codecov.io/github/python-attrs/attrs
+           :alt: Test Coverage
+        
+        .. teaser-begin
+        
+        ``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
+        
+        Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
+        
+        .. -spiel-end-
+        
+        For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
+        
+        .. -code-begin-
+        
+        .. code-block:: pycon
+        
+           >>> import attr
+        
+           >>> @attr.s
+           ... class SomeClass(object):
+           ...     a_number = attr.ib(default=42)
+           ...     list_of_numbers = attr.ib(default=attr.Factory(list))
+           ...
+           ...     def hard_math(self, another_number):
+           ...         return self.a_number + sum(self.list_of_numbers) * another_number
+        
+        
+           >>> sc = SomeClass(1, [1, 2, 3])
+           >>> sc
+           SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+        
+           >>> sc.hard_math(3)
+           19
+           >>> sc == SomeClass(1, [1, 2, 3])
+           True
+           >>> sc != SomeClass(2, [3, 2, 1])
+           True
+        
+           >>> attr.asdict(sc)
+           {'a_number': 1, 'list_of_numbers': [1, 2, 3]}
+        
+           >>> SomeClass()
+           SomeClass(a_number=42, list_of_numbers=[])
+        
+           >>> C = attr.make_class("C", ["a", "b"])
+           >>> C("foo", "bar")
+           C(a='foo', b='bar')
+        
+        
+        After *declaring* your attributes ``attrs`` gives you:
+        
+        - a concise and explicit overview of the class's attributes,
+        - a nice human-readable ``__repr__``,
+        - a complete set of comparison methods,
+        - an initializer,
+        - and much more,
+        
+        *without* writing dull boilerplate code again and again and *without* runtime performance penalties.
+        
+        This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <http://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
+        Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
+        Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
+        
+        
+        .. -testimonials-
+        
+        Testimonials
+        ============
+        
+        **Amber Hawkie Brown**, Twisted Release Manager and Computer Owl:
+        
+          Writing a fully-functional class using attrs takes me less time than writing this testimonial.
+        
+        
+        **Glyph Lefkowitz**, creator of `Twisted <https://twistedmatrix.com/>`_, `Automat <https://pypi.python.org/pypi/Automat>`_, and other open source software, in `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_:
+        
+          I’m looking forward to is being able to program in Python-with-attrs everywhere.
+          It exerts a subtle, but positive, design influence in all the codebases I’ve see it used in.
+        
+        
+        **Kenneth Reitz**, author of `requests <http://www.python-requests.org/>`_, Python Overlord at Heroku, `on paper no less <https://twitter.com/hynek/status/866817877650751488>`_:
+        
+          attrs—classes for humans.  I like it.
+        
+        
+        **Łukasz Langa**, prolific CPython core developer and Production Engineer at Facebook:
+        
+          I'm increasingly digging your attr.ocity. Good job!
+        
+        
+        .. -end-
+        
+        .. -project-information-
+        
+        Getting Help
+        ============
+        
+        Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
+        
+        Answering questions of your fellow developers is also great way to help the project!
+        
+        
+        Project Information
+        ===================
+        
+        ``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
+        its documentation lives at `Read the Docs <http://www.attrs.org/>`_,
+        the code on `GitHub <https://github.com/python-attrs/attrs>`_,
+        and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
+        It’s rigorously tested on Python 2.7, 3.4+, and PyPy.
+        
+        We collect information on **third-party extensions** in our `wiki <https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs>`_.
+        Feel free to browse and add your own!
+        
+        If you'd like to contribute to ``attrs`` you're most welcome and we've written `a little guide <http://www.attrs.org/en/latest/contributing.html>`_ to get you started!
+        
+        
+        Release Information
+        ===================
+        
+        18.1.0 (2018-05-03)
+        -------------------
+        
+        Changes
+        ^^^^^^^
+        
+        - ``x=X(); x.cycle = x; repr(x)`` will no longer raise a ``RecursionError``, and will instead show as ``X(x=...)``.
+        
+          `#95 <https://github.com/python-attrs/attrs/issues/95>`_
+        - ``attr.ib(factory=f)`` is now syntactic sugar for the common case of ``attr.ib(default=attr.Factory(f))``.
+        
+          `#178 <https://github.com/python-attrs/attrs/issues/178>`_,
+          `#356 <https://github.com/python-attrs/attrs/issues/356>`_
+        - Added ``attr.field_dict()`` to return an ordered dictionary of ``attrs`` attributes for a class, whose keys are the attribute names.
+        
+          `#290 <https://github.com/python-attrs/attrs/issues/290>`_,
+          `#349 <https://github.com/python-attrs/attrs/issues/349>`_
+        - The order of attributes that are passed into ``attr.make_class()`` or the ``these`` argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise).
+        
+          Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically.
+        
+          `#300 <https://github.com/python-attrs/attrs/issues/300>`_,
+          `#339 <https://github.com/python-attrs/attrs/issues/339>`_,
+          `#343 <https://github.com/python-attrs/attrs/issues/343>`_
+        - In slotted classes, ``__getstate__`` and ``__setstate__`` now ignore the ``__weakref__`` attribute.
+        
+          `#311 <https://github.com/python-attrs/attrs/issues/311>`_,
+          `#326 <https://github.com/python-attrs/attrs/issues/326>`_
+        - Setting the cell type is now completely best effort.
+          This fixes ``attrs`` on Jython.
+        
+          We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatabilities.
+        
+          `#321 <https://github.com/python-attrs/attrs/issues/321>`_,
+          `#334 <https://github.com/python-attrs/attrs/issues/334>`_
+        - If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.
+        
+          `#322 <https://github.com/python-attrs/attrs/issues/322>`_,
+          `#323 <https://github.com/python-attrs/attrs/issues/323>`_
+        - The hash of ``attr.NOTHING`` is now vegan and faster on 32bit Python builds.
+        
+          `#331 <https://github.com/python-attrs/attrs/issues/331>`_,
+          `#332 <https://github.com/python-attrs/attrs/issues/332>`_
+        - The overhead of instantiating frozen dict classes is virtually eliminated.
+          `#336 <https://github.com/python-attrs/attrs/issues/336>`_
+        - Generated ``__init__`` methods now have an ``__annotations__`` attribute derived from the types of the fields.
+        
+          `#363 <https://github.com/python-attrs/attrs/issues/363>`_
+        - We have restructured the documentation a bit to account for ``attrs``' growth in scope.
+          Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.
+        
+          So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.
+        
+          Expect more to come!
+        
+          `#369 <https://github.com/python-attrs/attrs/issues/369>`_,
+          `#370 <https://github.com/python-attrs/attrs/issues/370>`_
+        
+        `Full changelog <http://www.attrs.org/en/stable/changelog.html>`_.
+        
+        Credits
+        =======
+        
+        ``attrs`` is written and maintained by `Hynek Schlawack <https://hynek.me/>`_.
+        
+        The development is kindly supported by `Variomedia AG <https://www.variomedia.de/>`_.
+        
+        A full list of contributors can be found in `GitHub's overview <https://github.com/python-attrs/attrs/graphs/contributors>`_.
+        
+        It’s the spiritual successor of `characteristic <https://characteristic.readthedocs.io/>`_ and aspires to fix some of it clunkiness and unfortunate decisions.
+        Both were inspired by Twisted’s `FancyEqMixin <https://twistedmatrix.com/documents/current/api/twisted.python.util.FancyEqMixin.html>`_ but both are implemented using class decorators because `sub-classing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, m’kay?
+        
+Keywords: class,attribute,boilerplate
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Provides-Extra: tests
+Provides-Extra: docs
+Provides-Extra: dev
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/README.rst
@@ -0,0 +1,132 @@
+.. image:: http://www.attrs.org/en/latest/_static/attrs_logo.png
+   :alt: attrs Logo
+
+======================================
+``attrs``: Classes Without Boilerplate
+======================================
+
+.. image:: https://readthedocs.org/projects/attrs/badge/?version=stable
+   :target: http://www.attrs.org/en/stable/?badge=stable
+   :alt: Documentation Status
+
+.. image:: https://travis-ci.org/python-attrs/attrs.svg?branch=master
+   :target: https://travis-ci.org/python-attrs/attrs
+   :alt: CI Status
+
+.. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg
+   :target: https://codecov.io/github/python-attrs/attrs
+   :alt: Test Coverage
+
+.. teaser-begin
+
+``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder <https://nedbatchelder.com/blog/200605/dunder.html>`_ methods).
+
+Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
+
+.. -spiel-end-
+
+For that, it gives you a class decorator and a way to declaratively define the attributes on that class:
+
+.. -code-begin-
+
+.. code-block:: pycon
+
+   >>> import attr
+
+   >>> @attr.s
+   ... class SomeClass(object):
+   ...     a_number = attr.ib(default=42)
+   ...     list_of_numbers = attr.ib(default=attr.Factory(list))
+   ...
+   ...     def hard_math(self, another_number):
+   ...         return self.a_number + sum(self.list_of_numbers) * another_number
+
+
+   >>> sc = SomeClass(1, [1, 2, 3])
+   >>> sc
+   SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+
+   >>> sc.hard_math(3)
+   19
+   >>> sc == SomeClass(1, [1, 2, 3])
+   True
+   >>> sc != SomeClass(2, [3, 2, 1])
+   True
+
+   >>> attr.asdict(sc)
+   {'a_number': 1, 'list_of_numbers': [1, 2, 3]}
+
+   >>> SomeClass()
+   SomeClass(a_number=42, list_of_numbers=[])
+
+   >>> C = attr.make_class("C", ["a", "b"])
+   >>> C("foo", "bar")
+   C(a='foo', b='bar')
+
+
+After *declaring* your attributes ``attrs`` gives you:
+
+- a concise and explicit overview of the class's attributes,
+- a nice human-readable ``__repr__``,
+- a complete set of comparison methods,
+- an initializer,
+- and much more,
+
+*without* writing dull boilerplate code again and again and *without* runtime performance penalties.
+
+This gives you the power to use actual classes with actual types in your code instead of confusing ``tuple``\ s or `confusingly behaving <http://www.attrs.org/en/stable/why.html#namedtuples>`_ ``namedtuple``\ s.
+Which in turn encourages you to write *small classes* that do `one thing well <https://www.destroyallsoftware.com/talks/boundaries>`_.
+Never again violate the `single responsibility principle <https://en.wikipedia.org/wiki/Single_responsibility_principle>`_ just because implementing ``__init__`` et al is a painful drag.
+
+
+.. -testimonials-
+
+Testimonials
+============
+
+**Amber Hawkie Brown**, Twisted Release Manager and Computer Owl:
+
+  Writing a fully-functional class using attrs takes me less time than writing this testimonial.
+
+
+**Glyph Lefkowitz**, creator of `Twisted <https://twistedmatrix.com/>`_, `Automat <https://pypi.python.org/pypi/Automat>`_, and other open source software, in `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_:
+
+  I’m looking forward to is being able to program in Python-with-attrs everywhere.
+  It exerts a subtle, but positive, design influence in all the codebases I’ve see it used in.
+
+
+**Kenneth Reitz**, author of `requests <http://www.python-requests.org/>`_, Python Overlord at Heroku, `on paper no less <https://twitter.com/hynek/status/866817877650751488>`_:
+
+  attrs—classes for humans.  I like it.
+
+
+**Łukasz Langa**, prolific CPython core developer and Production Engineer at Facebook:
+
+  I'm increasingly digging your attr.ocity. Good job!
+
+
+.. -end-
+
+.. -project-information-
+
+Getting Help
+============
+
+Please use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ to get help.
+
+Answering questions of your fellow developers is also great way to help the project!
+
+
+Project Information
+===================
+
+``attrs`` is released under the `MIT <https://choosealicense.com/licenses/mit/>`_ license,
+its documentation lives at `Read the Docs <http://www.attrs.org/>`_,
+the code on `GitHub <https://github.com/python-attrs/attrs>`_,
+and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
+It’s rigorously tested on Python 2.7, 3.4+, and PyPy.
+
+We collect information on **third-party extensions** in our `wiki <https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs>`_.
+Feel free to browse and add your own!
+
+If you'd like to contribute to ``attrs`` you're most welcome and we've written `a little guide <http://www.attrs.org/en/latest/contributing.html>`_ to get you started!
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/changelog.d/towncrier_template.rst
@@ -0,0 +1,35 @@
+{% for section, _ in sections.items() %}
+{% set underline = underlines[0] %}{% if section %}{{section}}
+{{ underline * section|length }}{% set underline = underlines[1] %}
+
+{% endif %}
+
+{% if sections[section] %}
+{% for category, val in definitions.items() if category in sections[section]%}
+{{ definitions[category]['name'] }}
+{{ underline * definitions[category]['name']|length }}
+
+{% if definitions[category]['showcontent'] %}
+{% for text, values in sections[section][category].items() %}
+- {{ text }}
+  {{ values|join(',\n  ') }}
+{% endfor %}
+
+{% else %}
+- {{ sections[section][category]['']|join(', ') }}
+
+{% endif %}
+{% if sections[section][category]|length == 0 %}
+No significant changes.
+
+{% else %}
+{% endif %}
+
+{% endfor %}
+{% else %}
+No significant changes.
+
+
+{% endif %}
+{% endfor %}
+----
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/conftest.py
@@ -0,0 +1,28 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+
+import pytest
+
+
+@pytest.fixture(scope="session")
+def C():
+    """
+    Return a simple but fully featured attrs class with an x and a y attribute.
+    """
+    import attr
+
+    @attr.s
+    class C(object):
+        x = attr.ib()
+        y = attr.ib()
+
+    return C
+
+
+collect_ignore = []
+if sys.version_info[:2] < (3, 6):
+    collect_ignore.extend([
+        "tests/test_annotations.py",
+        "tests/test_init_subclass.py",
+    ])
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/Makefile
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/attrs.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/attrs.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/attrs"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/attrs"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..11b6e6fe3f99e87d7e7065d53c0fd6ca11f0a621
GIT binary patch
literal 7639
zc${@ucT^K!u-?$6NiT|&AiW5Lj#NX7)CeNIhh78(=@9`bN;4pVAP@wkD@8iQAD{xE
z8I+D70@9@$>WlCF{q8w;_w4RHbN0@BGv7Bi*~~<rf%Y0L000bz22eQZUPqc4G*qN(
zOqUu1=|&N(V`xD`8c{TENu=j1fd<yW0KnM$Z-TNV7_XCFa);<X2)P%42nllzatFe~
z!lb?YeS%$`1Kp(qf;{rKZ(jp|8y1F8ZHvd4t%ArX9;@)<*b($M@8v3AkzJvzrr=O1
zl__D9pIN>A*Y!bUg+ME}C=*!#fdUS@Vi-Pi?6>H*woGV#(#)BsIBLe<sMSg_9UpH?
zCYw8{<r2s0>+9};c$DmyFq!2(XyX+&LeLCl8~nOm9U6+b-~M`MwIsbWco?(4x!vF~
zyxhv9bB#r8nG%0y3q6oS3V^3wxe~8veY5>glr(OO%-FlhlI#O)D$p;Lvh;P|g?1~Q
z$DxZiPM875ddZDiTzs`)@t(9yL}a8OIXU?b)gNs{7Zc7EsHMh1D!qUYLtU?u?t>oN
z^T$a8Z|BM4F<`Wjzd0<L1y_ciNdEm4e|8h%XaY3^t^>1nl?>pMY-g@jYJUH@E7@UB
zGK_=+EMU>($hYq+9O(r7jmin_2nVB^ECu-6h`g2e*Jd1L#!s{p9>>GI`8tRi!}noZ
zDEM{;yo(lKN$#D=C8_|Lo9pW~8`^wyae=?=wG6}x$pfD}GZsf&h%x)2yKwk2_<Ga%
z`FYN>^YH}I3jS)_W`AbnOI{@jgt#=ol=&PAPB#>L6*+6Q5I@ik{9aqLzLwh8MZHkZ
zf8x5bpGzDR-L{dJTx=|6{M{Bqj`V{bm?`F;YfExO(%>F}Qk@%{n(Q0HcIH_@#~a+m
zdsIfZdDE{59Dzo+?%o5ipj27P1sFlT@JfrL>6LN@&>d(j@?HuZMO_Q<r(e&?j-Mx-
zTqql+D<UjR1;X6(Fh5CPQeO*+s2>g*hbr$;87St@C7wt~10iCi{&mevO)TO8NSXci
zb*Ucun7W|s1Y>1CnhYp7v$6TD;>0N49GFS|;<l0vIFJ_&y4!Li?gRYsEn_mz9l!Jc
z+v0VZqmJW!V1E1OQ7rIh%=+j`UhPI0>)2<ro|h=}&O6Bsi2qI<^<txd)6w>vS|9$Y
zw14M>{7@u}>pPp|;E64;Q=uyju3Yvq<J4KMA%~(W_i{7{@|v3#ALWNe$o{^MtjKt~
zIN!!39uWP&-q;-+L8Il&d#JHx19oy}IR2h6`V!2qs;W9|V{7YJhibc`Whqz(_wM3w
zT3Z*2Okh`-wIvy5Nc#T$`z^>m1N2*6W&)GX%Dr^-OqGAfZ-)bV=ID=oosWbp<`=j^
zBLyLjvLg|lmKVw~#?X9*p1=lVwb~*^i9eUvcg??BI@7>z%|am4AcSQsvdZ`P)r-m&
zt+yT8@WCiH{F#FN#4Sn7vW*jO;0L8y%@bmmja#MCV&liMswyA9n=Dhk_*Kwv0c<wD
zg9pa~umWW$c(a=v+FWT%ADp>GjOyZGo?scf2Ol^^!LM@CCVV9QwKO6|3KIJzr^zPN
zvbH|(is_{98<Zp2AI5T3Xh?xG*N=+gbaJ3=ZhnXSmRRY0LBCs<ZGiSHlgV`x^Rel>
zV#~@q6e-f`qMD)|g-P2tv1(}I{x{m+<f}9Syo5)cWrWrU=KozQgh}Gbwt}_Zel@t2
z0XG5uW_ab57d%Fn5qqDvIf4RND+|jbUI@o?RW{JqdU{fK5Hnz<*!K^74<{!lAC^Z*
z+^_yCitJ<0Q}gIcVZR?48R;Jq5<&sdkr8^?f1m9R#GZD+4rs1<@ZM!<Z+S2*JuPh*
zEt3BHxlyfMYdx?qE&d~GVpG<5|LTV1Q;EEY;p^$2mvoZqfZr}F*GW*Bsul<HraKD7
zKUPB?W81vOuK=bjV-q4GBHi3C^e(werTWrX$08Ye{svtkN^|OrKN{=B2Q*1;6h{m!
z<dj*?L%|POA+a3gY3io50{({OgsaB~2a-dp`#Y{H?PfeeKDx#AKVxBHk4%MM_LFIe
zXWPih%I?x5d9R*?4K^DQqFisB)+Lu=_F-b01Nhh!LM$c~1$UfybWV5g-o1c&LGg&b
zD80=e>^e7{O<9G7g|~Izr3?ZW@ku!{`?jEJ4xL$Ur_0xbkb$a=6Gz~0w|j~L_@q#K
z0Fr8TI}}X>f!o+|^}xvxWdD@3U#rtY{kHb@k@by@NEKvPbV)@SLtE{o5OegK`5;tr
z5ekNoL*ox9?I#@dq3L9vyRAFN`DNkVBheHlgw6d=OS`EY?|zcu1R?C40Lk8Krk^#y
zCl5Ofss(K})58w%nzJokVww58k?=kpBBq68^eLK8;*olRiKb-yB<+GOpov&9F7ukt
z8C=f7NAY#^%RYQb?I)?6=RM6|YIk+P3gGx=o5RB6x>A$lu5MKsxb*2o)zQum%@k{1
zR``IqGM^g;aGJdQVNo`mUO%<@i25q;fhw)Ly;oi)CtyV20RjU$mqmMmz$b{-h2hiV
z9nH9}wHof-f{2)Wue`g)gbvTBs52N4-7sJb1dyPyj<{!6)Q_4#2+Wk%PRMU*TO9XF
zDEV=@uIQIJ(qFxr&N?Oq)B|0*(3$%rRE^0G)wOemITI`hiEeUJ#i$5g<n^#z{#Fr|
z<S2nxgMRO~glhS*nh^S>{wek?{?WSkFDrb>n5Y9ekO3rLZfu@ZSa=%>K9j0REQ33U
zEtde-7JJ_!twV+IXEz4x<I=`vM&)1Z!Ne}9kv9%=N;q<f;KBN%aoaS#Vgb|1tbVsR
zbbB_OdY^b!$C})GmqdD(b@M^rXqls#a6A9EOrK=eV-({Uq?kWc>;0(!r0Ll^M+I77
zIOO<G#O3{i&2NVsSaZ)7uRFJSyV-CZae?Ds1OPP<4`QVh8lsU>Y<M1H)5(l0(KsGs
zlgI3N=N$<nYVF3<yUk$dg{0srBeveJx6KR1|D}OkGM+_xEBxo*A6K+k#y|q{orfBe
z-@k{Vw~W<R19A1$F+DQ#g7K47JFFMR`yE?wHu#f7+jnbi{!Ez%ZT5(m$@*%)+;V~E
zwHpBf*t-8pMn*LTIzQJ}>x<uK1dTcg2FRg(g}}`Mn+3|!@sDp+zFQy>zX13okFx->
zPmagXvNx+<gM|mTV&bpzj^NK!dSo0bk2Jt1*VCbYry2unznpoAE=#e|_5d1Bupd9X
zYO$FsC#-pZ#(DO&XckK~=eY0O5!)FZ9W^J&@A&)pZ17{%K##LJ6VZ-u7msYV3$`^a
z9604sdnOJ4Xf)CJHV(gYfK^A-3bDEkrAH$CtD0UwG7~N4y=-i3Zp)q%<ZE&!8E!Nk
znL-^-stRP~8~4b#_OZ<1phq`-KKIoqHkgWS=fGoRNyTSd+t!1gp{64`I$D3>2^nPH
zud}n$-{krZQmN3H4JW0OWDg%GCq=sBzrTfRBuiK>$`vH_mM;$%S4&mi^`E<sF-aF+
zo_2Zc=L4$Ip3Q5Xp3B*A|EH%N?G3F~6NekC_hd5-FoN-qDKvE3$K<!XG^hO(uWdi(
z0z&L}1hA(I(T_*Tklh1_20jUod!$0r1U;w&zDLd;%ikg)_JgUP5<5eA$A|WjV{?qi
zNuK~Bh6cw7R*lEboDg9Z`N^KC>E~p+p9c_4R!68rNHQibZ<_PPNkub|(tlS2SRf~G
z=5qV90BsAA+96-*<!3Qro}Rx&Q@rx?GCSbjCQR!YUr8vNKGW}{{FD3{g2pdK(2a@8
zzu8MmPuC>F$$o~C2lDixY9VtvT@OU8uVs;3y|}pe5OMJ=V$hrpf5t(OpU5qn!lG;j
z@%iyg4Cw1v2l-k59(nk{GT!$w&udN5F8{n0+jS{u3E^EuwVs}yuvLu>C8ld4A2gco
z)R8(Dl#34Fx^d%1ot;8&a^j45et&OouV$aidv@S;|8h_}6*cwMT8GYv-JgA_VPv9c
zx3*y!WSDi1f?a@i-jl=vMdh)%IVn<BngCTBQiHxpp83vPm(~F)c~8nL9DoeC0}gL0
zjZ@jh{r&rw*wfQP5^Q?cLWgaWGcz+^-LJ7}qQ`xY7?ew0mXeb4CefUU<b1}u=a-s#
z;^@rGb?23^Z?<W0rm?wy<)d@iGbHbR`043sKwNB&W0M&WxQb@!oHZrAOknDE4B&<Z
z!fAF91o>`@f4_&YbohwZPWKfQ{e%+r$PY|H*OADIPSMJ)WUP9i%81bM8m&psZ&kb<
z1NaC3;Q(%b#D_gkPY=s03vY&d(;=f9*42mcoVGcKkXPsNs3NcCfigqkmz)x%8qdC7
zIXa>d_)tbz+kJfdLR_Gi9gqh73B|jLB%(Eat-m{tbalm?pRnhZML$g<efeDG6+=7k
zR!@)CCP>^en+@mU;pr$qU-FbQN>c^3&gOjl`0;7O#$B<nlDECtEpsH%mLqN;{IaIi
z&fh&Y_EO+D>zVRc<7o$^-%ZZj&CM;$)^_Hc<h35)JFUPxrr!+@KeVc~3kM<Zu8tj)
z>+9*k5+hY34m=kp-@7iR#ie7I1p}H4#D0K^ex@OrT!K{#F2VtD0;;H(A^0o~9hS3e
zl~3VR+`_L0@ZWf2Cs59s*5PhY3LjhgoPLW#M>1WnM1+*-pZ@7j%Yas$|8J}tH59(f
zL<Nx}hoB)>AdX*2WzkjLJ`i4EA)i0w05~<ky&=rHB=>hZ?8(a+d(8X`H8&}-G3XyK
z=!{c&o(lr_UG?ne^igLWyN;cvtn5R<%@E+jdGERQAVE?-d+&%OIBsxpagn{Ne(r3%
z(8~LB_wPmmQ#x7Fq-GI;A1iYie>ZP{L9}q>QkW+T!!%|}&PELsc}?F}re`5WX#}G4
z5=caZ=)zZYn2Azs-}!_$(cAh=f>x(N-y^-R76Wu1JI^W%ZtkVxZi?xMu2@QZ9$`B9
z(7Iv-pC|PJZ8;QEmV|HLnwYiP?_`hWw@~Hjg{!HlO@IPn$QvzKD41DavnBBT{p-dh
z9i$+o8I~u%<=vmNQpxto<4EgJScs^qZf;g`vA$nk=9PsOLLTZQDVC1I!8UO&<9V`R
z)sJ`)kXx?GjTd?ZQjxqBal~k{_*Rr8ZFx&DgfFL9)51HtBwF;*D4a-OA{k&VpyU69
zPmO2V0))}wJUCedFRM;2G=x8YXq)fawY<PFp+{k1b%V=6U$^g3kAA{$K^v2ju&@eq
zu<8{jG8{joJR81tad81+d)kioYfMp!3N1GKErcBe*(JKzJyqlkpuk5#X?EElmu6yz
zPmP2jbqN+ECbc0nzLxXr;#)2$<^;|(m|LxR7`PI53xX2h1GbxdK4R$YJ4B;`)1=?5
z(!W5E9XaEDBJumq_I6H!j&oJ#^#)2U+HET7QWVTx4t;Kt9T5?cvl7%=g(k|`I!Y&;
z-X0PGHo@2%__H@&d1}tLf^K9tH#7v>%U|ovM%U@@8lSSSE&|9)5=q}}z3p~g$=^;v
zLE(@L$*$3F#sY35$ULWpWv{hesDf!#i;G!wLdpqVJr!_p=3fRx7bWfxi0LWm>JG?T
z@wv<1yR;iB(5se9qyo25X;tY*53lF~j?OF8Jh9PZo(BPBDGz7J7fSNV)TxWyzMS^=
z_iGAawb(gvgD+pcymTlt62xBey_MJHt$5P{;P8%m591HBx+Sc%ps}C@a?qO=bxs@~
z)7Z~?x({h-X^M!h{s2RS4pgwT_O6%`)7WVosxN2NabRdS#J-yWM+r)$D7EN23r>c*
zKsu~u9rMbFTX4$V$59N-bBhmSvme;s54W|ksil4=ujO2XQiZRpGM_UebNJmsGE@tE
zgD5ZHLRrhYY$~(gE#^T53*^W8qtBteL#>~<fq15{kIN_JR9N@yS4s2CZ*Bq^gUc(T
z0byas*7&pGB6sJNbne7mctyRN98nmYrJec)b<zXN{&(ges@HmuRY20JKSzHAM9%(*
zTF45XKirt80|M=nv69Tzc;D$M5o6#Moq&mG=_k;07V{kR3?M0j4Mw+8_fht603Iqd
z_Y7{b++tv4d>txVIMPl1@v`!nu!zWImt8S@y_P=#1RyTFg<n>9_Cl1EAG3>#m>WU%
z{lU{$7bw|+`O|3ygmYEOP&P);yXf$O&KF0Xt%)b6r*(qZCiau-RDb9pJFGffqQVg*
zS-k|S(A7z@#CN15-JiA%6JvYyNsN8!f#LEu`K=*QjWSZBEe*7TZo6`Qsxw23_-Po3
z9T%O2e*d^N)4b~8coSPFS{hSjm#>qQnoBet$YvcQB}=-Mpa`Tp@Xx}>m!t*+alwW(
zRXHH~0|7yz-FL+-Eknaf$J1A>cZADzS(!UO$~?~swj#xN1~Y&rG7i>jER}6-Z*Mp3
z5zlmiTBj8$Xd@*qsQquC;WE3<nOjiM-ez%eaf!dG6gkL18Fdxa=E;@lI#5n3Ep?+i
zPjlS2s?Ex;(dM{E4^_mW_S6~sMCf~3_!Q{L7@3$=r0UZ@yS(3toZ>@f*?K5GYin!M
z2(Zqn`gD47;z0E${+t^4cO>Mp;sZL!ws7c?iCL>uqJD!a065ev#40+n-#P{5=3_8X
z-;LW97Ca60pzVw4Kf$E7{Xe`3cLwjV+=HUp4bK0FV;3*rri5!|Yau7rzz7*=T2W?S
zuRJL7Vv|Z_IeLEiVVRF_VQc;A7=AN<w>3KVSFfwXkv_C8RNP~QFMR5UFWEf@f%uX=
zx&3+XndhIEd=@Z1e#P8{<QfCL__|>6`>^Q2CFvWc+w#Wy3NgEJ+~FV#b<1QZIPEp-
z+oyZ90zB?4I)CN%NNrjuSlkEGi|-Z<J8&RR`{8wyW$n17n=lg=e9yyUwawsW7LyK!
z&WRgx%XuZ2Nk?X1nfZl8X&M=qC>!l9dV2cjIO2<y>FMdp&}S8HL89>wJP{eszOEf)
z@SS_-g)#)M)tCbO*ve456~pFxi+Kt@X<I*bJbUi3W}{w_{HsV>q{8g?o-bJ)aKTAz
z|LRMD)^#9Oyqf(<G5g&XLor<bDB7QcCHt)B>sn)Mq1_b{C5E`T{KN(=<2P4Pi_^50
zU90Y~`APx)+}zv`r<*_HaJZ3Dlqvb+w4@W-ggEwOXxbaXTFD=$F;)HWtwvK@&@3LW
zME0A)#A@7I0BdPNGK2{0d1yI;@2XiJl$Dize`WZ)MU2Lk+8|N23RJ|v;BXp`O8tYR
zl!jrtO-&g{Jd+tr8+L^){f7vesLWizkHsG>aw667XCb8ST~$FtKb)GHy88>>{VL^|
z7LEC#i}A#vzjF2Z#xIqJUJ2x6j&50SY3jhPJK2Tz;ql(ddL^m*#JtdtW#n}j1^^#b
ze17ey+gfayfIqWGT)2>o;{6vI3r%kGrbG5W*xU?C&d+T2uZp0?xWQo+9%sMD73FTJ
z(aY_3n5<T6i!Fx*p}CM%Nx$pjnaWUm`fasgL)C%P*FV%eB}>OHA*T7`-~O6?kX}Gl
z-e=<|{F3{y#1Y)~Uj+uOULXf(X%uR1KgwrhLRKMm9&}<mDlEhY@H>oVMP4SkW!8rv
z-2}^X=N=iV8w2Y4u+~{c9r0(kN~D3wTE%6vOar%96VV7cbhTgQI?>Blg#`c2#tFiu
zO8DNd4GF1g;1}qOHk3>qtV-i#B&Oik`;~FQ7rgGaVhjyo7PpN*XS0;9-gJs6WpOrT
zJ;;=aXCg^QjFHjmR`7ix3+D;<Sk(gF{Swz684bpTrMB7t79G10ycMX0Ov|va%f=j5
zpx7Uy8@}q?9l0?+KCV=$;=i@E_2S_8_=h3%@|Ri7y1q?pY-~!B3!`OR+H>+!lq=Rx
zNhE2%`0GP?vfNYKoF-}<Z_(d^s|*Z>fPr}FO~}Rv{h;S)t0LuB_WqYOy6h%Y4eEYi
zoW$u%CFR*yvc~&&MO_UY)Bd;anbvzOAvE-$*=$0A%are?Dn^$>opi;w0{Isbs{E1~
zCN6?|=Ow<Qi8?d0yP+7laLgTz!osQ!5!2Tlm?Vs37w?_y6g6YYr58H#*B(Lmf-Tre
zQI6@JBA6Jn&r*Xy?g~m*IcEQR)U`R^sYYEiauI0&o1%L*Voqp$3UQ=!a`>)YJKOG;
z{=8nq1bD3;xPZ*JtRh4yb-VRvrOY=ud>tDbqgpNb8XZVns-s|55exXx*?F#_d2*Pn
z%jkZKq~d*764od``SAGXrRbH+%o%+B`ZY)a<Enmp(QGCDWkEc5NZf|O&9CGhf+CJ(
z^dxN1KKu9tUD%}GtcP6S@$vt$N#`=x_9%7X>^CuaaEuvN;e#2N<4O$)3oE4baBRUX
z&3WhD?{^t~)n`+>^gH3Ud+v+cjg3X$OF66Xnd#}_JH!mA)>!NiJA0X+2wj5Cw{4YH
zyX_9z=p)M+M2&^qu?1|1xS+lsCh%IWO@|;~z9y99ISjTd(g~ym2Pt^eGxQC9Fovqx
zy>3>!L4i};q(Q+3s=V?X<rFqu{1jmoHKdF`dpb2i0a4=)0d$p?D+RDVi%fLJVu!{`
zTeJc{O_mSM9Kb1RY^yUfGtsgGp*GdB`^-HuWS5fOxc)Z{Bl1Hk?F!5myy17L=HF5<
zcg`2*Di}vj8n?qLOv<B;1>$WTl_w@eQkGMw&mYM5k;<&HX2R>l&uTpI0VN>U0;axP
znr$P8HWDkv=y+rwJWi9@Z#5Y(6J@LX5ZQ259TQ{N?(;`9aL-!8$i$>AC47cJXpk=)
zainp2>dbX@_*mn#GlyYM8(MoDwzT{ATBNi=gR9h}w5EHvvSt}QG(-gQgFpTYOsw2B
z;<heyWc*JwZRxl=`-zm70iOUio83Rk0Y;Qep=;J)E2-fj4>Te)(jeVQ;*y(L2)KzR
z{!C6zPIZp7Ma7{ntNyzllvPeBevqnHa?4PxP#9ZC{#$~L_WVP&2++tun;@i<R0^mj
zza_^}A-a@b4H~jd0LSEy1H1Mx$`60q851yg=DHj>FWI|P^zpR0cj8vbupv-`pH0Ks
z8&D&q5gH>O=&W5MOHp$77^2=QE>&=n2MR$PuPnsMLf)WUMW;JA$pew-t7KdpIy~mE
zWi&d;lRG67b(2{LWQW6Z_NavJcov(9a`UOg4ea{cnw=Fn5Z{&QdzX%T)p=ZY^FV)b
zHI|T>JS0P6&A%w%qZj}q+f!4`?;bXnuO5#>Ag}0ZtysXS9=zYa11;zz|Ipu&h<u2n
zO`<bCnN>KxWxRqx^Xyo>W5QjO>P9!dmTm!ccha{rx6Xr+S>3`v1-uYdxkzoAU$*A0
zHpsZX67t+$;>QQcs_AH#a$ik{Kt9-mIm=1m1`;vWMP~>F+6I)4cV|nmifXixJx%Ib
zIGXQycJ<b?^!}~{Cce5OUJ@xA`~>MIFa`gNG8CKW5y*X=p7#35%5y<%;e(u}61!F>
zglTlXc!m`$y6<p^*il$A%eod45!s|PwGtDv`yT$G#ZG`ALVQ&gI$0ii7mGUaDG&OH
z51{&^!q%yBj=hTO1Zv+&4jxjEPEV`SEEM3+Zam)Pu11|C<&(M&O;8jKa)L_cS;N$3
zwEF<hk|nO3ViA8f189I^v;XR{aSlz`<X9>;ipwl9clgh>>Ha$^gpLp5J4LTWwHOxt
zCn~-njhOGptBJd(KfGgQH8E{)GpMFhW`mL`Y--#Pa8xiR5t)1a%iNAcbe#aUCJ=p=
zfKCVg{#e#|3KQGWC(cm~BO0vK;yr87Ykz296%OF_CijQ=TJB7JTOaI!*K+hrtGTNe
zAyY(G5-sY2GbtbeW-uXN&SfEhgL#Yv!Rxy6(gI)bX!59CseY<@ah2MN)IX$UlPKyb
z6FAvL^jn`De!q#U?^uOufeYyh4MCv^&~Xp0PfSsARz|`vZ{RyXII75eFJ{OsW}QZf
zQsH=}&*t>?xAmaUXcKfCB{?FUFE7r82-hVod`tF}kql6j+e#C@BPof4bxaPS@x&#j
uX$cx)A?EIthxoN0PPkP*o{>R06icoV?~v2i!xjyYhM}$rv{uI{?tcJg2!{gz
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/_static/attrs_logo.svg
@@ -0,0 +1,1 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 142 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><rect id="ArtBoard1" x="0" y="0" width="141.578" height="117.638" style="fill:none;"/><path d="M32.55,82.516c-0.191,4.027 -0.832,8.503 -1.793,13.238c-1.023,4.793 -2.301,9.269 -3.836,13.234c-3.324,1.215 -7.035,1.922 -10.808,1.922c-3.77,0 -7.161,-0.707 -9.973,-1.922c0,0 -0.387,-3.133 0.637,-7.863c1.023,-4.797 2.687,-7.93 2.687,-7.93c3.324,-1.215 7.098,-1.918 10.871,-1.918c1.856,0 3.645,0.192 5.309,0.512c0.445,-2.688 0.832,-5.309 0.832,-5.309c0,0 -2.496,-0.703 -4.988,-0.703c-4.864,0 -9.465,1.086 -11.45,1.664c0.579,-1.726 1.09,-3.328 1.727,-4.925c3.328,-1.153 7.035,-1.856 10.809,-1.856c3.773,0 7.16,0.703 9.976,1.856Zm-15.348,23.277c2.75,0 5.309,-0.703 5.309,-0.703c0,0 1.34,-4.668 2.109,-7.996c-1.152,-0.317 -3.132,-0.704 -5.371,-0.704c-2.496,0 -5.437,0.704 -5.437,0.704c0,0 -0.703,1.664 -1.215,4.031c-0.512,2.301 -0.445,3.965 -0.445,3.965c0,0 2.554,0.703 5.05,0.703Zm32.676,0c2.813,0 5.629,-0.512 7.867,-1.024c-0.769,1.981 -1.664,3.774 -2.621,5.5c-2.047,0.383 -4.16,0.641 -6.332,0.641c-3.773,0 -7.097,-0.707 -9.914,-1.922c0.129,-3.965 0.77,-8.441 1.793,-13.234l1.918,-9.082l-6.461,0c0.707,-1.789 1.473,-3.453 2.496,-5.051l5.051,0l1.344,-6.332c1.918,-0.703 3.965,-1.215 6.074,-1.535l-1.66,7.867l11.125,0c-0.703,1.789 -1.535,3.516 -2.492,5.051l-9.723,0l-1.918,9.082c-0.703,3.262 -1.469,9.336 -1.469,9.336c0,0 2.493,0.703 4.922,0.703Zm27.496,0c2.817,0 5.629,-0.512 7.867,-1.024c-0.765,1.981 -1.664,3.774 -2.621,5.5c-2.046,0.383 -4.156,0.641 -6.332,0.641c-3.773,0 -7.097,-0.707 -9.91,-1.922c0.125,-3.965 0.766,-8.441 1.789,-13.234l1.918,-9.082l-6.457,0c0.703,-1.789 1.469,-3.453 2.492,-5.051l5.055,0l1.34,-6.332c1.918,-0.703 3.965,-1.215 6.074,-1.535l-1.66,7.867l11.125,0c-0.703,1.789 -1.535,3.516 -2.492,5.051l-9.719,0l-1.922,9.082c-0.703,3.262 -1.469,9.336 -1.469,9.336c0,0 2.493,0.703 4.922,0.703Zm28.137,-25.133c1.984,0 3.84,0.191 5.629,0.578c-0.703,1.727 -1.469,3.324 -2.367,4.86c-1.406,-0.192 -2.813,-0.321 -4.348,-0.321c-2.492,0 -5.242,0.703 -5.242,0.703c0,0 -1.856,6.075 -2.496,9.274l-3.067,14.515l-5.82,0l3.07,-14.515c0.957,-4.735 2.301,-9.211 3.836,-13.238c3.325,-1.153 7.035,-1.856 10.805,-1.856Zm28.715,13.621c0,0 0.445,2.621 -0.574,7.356c-1.024,4.796 -2.559,7.351 -2.559,7.351c-3.328,1.215 -7.035,1.922 -10.809,1.922c-3.773,0 -7.16,-0.707 -9.976,-1.922c0,0 -0.383,-1.726 0.129,-4.988c1.406,0.387 6.457,1.793 10.933,1.793c2.497,0 5.375,-0.703 5.375,-0.703c0,0 0.704,-1.153 1.149,-3.453c0.512,-2.305 0.32,-3.454 0.32,-3.454c0,0 -2.558,-0.703 -5.051,-0.703c-3.902,0 -7.226,-0.64 -10.039,-1.855c0,0 -0.386,-2.879 0.383,-6.524c0.766,-3.707 2.43,-6.585 2.43,-6.585c3.324,-1.153 7.035,-1.856 10.808,-1.856c3.77,0 7.161,0.703 9.973,1.856c0,0.128 0.387,2.046 -0.062,5.179c-1.536,-0.449 -7.165,-1.918 -11,-1.918c-2.493,0 -5.372,0.703 -5.372,0.703c0,0 -0.64,1.024 -0.957,2.621c-0.32,1.598 -0.195,2.622 -0.195,2.622c0,0 2.496,0.64 5.117,0.703c3.774,0 7.164,0.64 9.977,1.855Z" style="fill:#222;fill-rule:nonzero;"/><path d="M79.12,44.539c0,-0.914 -0.066,-1.805 -0.195,-2.68l2.832,-1.687l-0.058,-0.266c-0.434,-2.004 -1.145,-3.902 -2.086,-5.66l-0.137,-0.254l-3.156,0.723c-0.766,-1.235 -1.676,-2.375 -2.703,-3.395l1.285,-3.012l-0.2,-0.175c-1.5,-1.293 -3.168,-2.391 -4.976,-3.246l-0.254,-0.118l-2.137,2.442c-1.386,-0.551 -2.855,-0.934 -4.386,-1.137l-0.727,-3.176l-0.277,-0.015c-0.477,-0.035 -0.965,-0.059 -1.457,-0.059c-1.52,0 -3,0.16 -4.43,0.457l-0.277,0.051l-0.293,3.25c-1.465,0.41 -2.864,0.996 -4.164,1.73l-2.438,-2.132l-0.238,0.156c-1.672,1.09 -3.188,2.402 -4.496,3.894l-0.18,0.207l1.684,2.821c-0.86,1.125 -1.602,2.363 -2.188,3.675l-3.219,-0.289l-0.093,0.266c-0.707,1.863 -1.157,3.852 -1.313,5.918l-0.023,0.266l3.035,1.296c-0.004,0.047 -0.004,0.098 -0.004,0.149c0,1.289 0.137,2.555 0.387,3.766l-2.782,1.656l0.071,0.273c0.55,2.028 1.379,3.926 2.453,5.672l0.14,0.227l3.223,-0.731c0.703,0.973 1.496,1.883 2.371,2.703l-1.277,2.993l0.211,0.179c1.609,1.328 3.41,2.434 5.359,3.258l0.242,0.105l2.176,-2.48c1.059,0.367 2.176,0.641 3.313,0.809l0.726,3.187l0.281,0.02c0.563,0.047 1.149,0.078 1.743,0.078c1.554,0 3.07,-0.168 4.535,-0.481l0.273,-0.058l0.293,-3.254c1.113,-0.317 2.18,-0.738 3.203,-1.242l2.477,2.164l0.234,-0.137c1.813,-1.07 3.449,-2.391 4.875,-3.918l0.196,-0.203l-1.665,-2.789c0.774,-0.938 1.45,-1.965 2.028,-3.047l3.293,0.297l0.105,-0.25c0.821,-1.84 1.391,-3.836 1.672,-5.922l0.035,-0.285l-2.976,-1.27c0.031,-0.429 0.054,-0.871 0.054,-1.32Zm-12.921,0c0,3.156 -2.555,5.711 -5.711,5.715c-3.161,-0.004 -5.711,-2.559 -5.715,-5.715c0.004,-3.152 2.554,-5.703 5.715,-5.715c3.156,0.012 5.711,2.563 5.711,5.715Z" style="fill:#222;fill-rule:nonzero;"/><path d="M103.519,15.5l-0.067,-0.242l-2.253,-0.012c-0.317,-0.934 -0.747,-1.832 -1.258,-2.664l1.351,-1.844l-0.136,-0.191c-0.809,-1.121 -1.778,-2.133 -2.868,-3.004l-0.195,-0.156l-1.84,1.316c-0.832,-0.578 -1.75,-1.059 -2.722,-1.437l0.011,-2.266l-0.23,-0.074c-0.328,-0.098 -0.649,-0.192 -0.992,-0.274c-1.028,-0.242 -2.063,-0.367 -3.086,-0.402l-0.25,-0.004l-0.715,2.16c-1.028,0.047 -2.047,0.215 -3.024,0.496l-1.308,-1.84l-0.238,0.09c-1.317,0.469 -2.559,1.121 -3.692,1.934l-0.195,0.14l0.695,2.18c-0.742,0.617 -1.422,1.313 -2.016,2.09l-2.14,-0.711l-0.137,0.211c-0.773,1.156 -1.402,2.434 -1.844,3.816l-0.066,0.223l1.852,1.363c-0.012,0.02 -0.016,0.043 -0.02,0.059c-0.207,0.867 -0.312,1.726 -0.34,2.578l-2.156,0.684l0.012,0.246c0.054,1.468 0.308,2.902 0.761,4.261l0.075,0.227l2.308,0.012c0.313,0.75 0.692,1.465 1.137,2.148l-1.348,1.828l0.149,0.196c0.886,1.16 1.929,2.207 3.129,3.074l0.191,0.14l1.867,-1.335c0.641,0.402 1.328,0.75 2.047,1.039l-0.016,2.281l0.231,0.074c0.387,0.121 0.777,0.234 1.18,0.332c1.062,0.246 2.121,0.379 3.168,0.402l0.242,0.004l0.718,-2.164c0.782,-0.035 1.547,-0.144 2.297,-0.316l1.336,1.863l0.231,-0.074c1.398,-0.43 2.73,-1.074 3.945,-1.887l0.203,-0.137l-0.679,-2.16c0.648,-0.504 1.257,-1.066 1.804,-1.687l2.184,0.726l0.144,-0.191c0.86,-1.117 1.567,-2.387 2.09,-3.762l0.09,-0.238l-1.816,-1.336c0.086,-0.277 0.16,-0.559 0.23,-0.84c0.141,-0.613 0.238,-1.219 0.293,-1.82l2.191,-0.695l0,-0.239c0,-0.086 0.008,-0.172 0.008,-0.261c-0.004,-1.34 -0.183,-2.661 -0.523,-3.93Zm-10.766,3.941c0,0.301 -0.031,0.602 -0.101,0.903c-0.43,1.8 -2.032,3.015 -3.805,3.015c-0.301,0 -0.598,-0.035 -0.898,-0.109c-1.805,-0.414 -3.012,-2.024 -3.012,-3.797c0,-0.297 0.031,-0.594 0.097,-0.898c0.422,-1.801 2.032,-3.016 3.809,-3.016c0.289,0 0.594,0.031 0.898,0.105c1.793,0.418 3.012,2.032 3.012,3.797Z" style="fill:#222;fill-rule:nonzero;"/></svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/api.rst
@@ -0,0 +1,396 @@
+.. _api:
+
+API Reference
+=============
+
+.. currentmodule:: attr
+
+``attrs`` works by decorating a class using :func:`attr.s` and then optionally defining attributes on the class using :func:`attr.ib`.
+
+.. note::
+
+   When this documentation speaks about "``attrs`` attributes" it means those attributes that are defined using :func:`attr.ib` in the class body.
+
+What follows is the API explanation, if you'd like a more hands-on introduction, have a look at :doc:`examples`.
+
+
+
+Core
+----
+
+.. autofunction:: attr.s(these=None, repr_ns=None, repr=True, cmp=True, hash=None, init=True, slots=False, frozen=False, str=False, auto_attribs=False)
+
+   .. note::
+
+      ``attrs`` also comes with a serious business alias ``attr.attrs``.
+
+   For example:
+
+   .. doctest::
+
+      >>> import attr
+      >>> @attr.s
+      ... class C(object):
+      ...     _private = attr.ib()
+      >>> C(private=42)
+      C(_private=42)
+      >>> class D(object):
+      ...     def __init__(self, x):
+      ...         self.x = x
+      >>> D(1)
+      <D object at ...>
+      >>> D = attr.s(these={"x": attr.ib()}, init=False)(D)
+      >>> D(1)
+      D(x=1)
+
+
+.. autofunction:: attr.ib
+
+   .. note::
+
+      ``attrs`` also comes with a serious business alias ``attr.attrib``.
+
+   The object returned by :func:`attr.ib` also allows for setting the default and the validator using decorators:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      ...     @x.validator
+      ...     def name_can_be_anything(self, attribute, value):
+      ...         if value < 0:
+      ...             raise ValueError("x must be positive")
+      ...     @y.default
+      ...     def name_does_not_matter(self):
+      ...         return self.x + 1
+      >>> C(1)
+      C(x=1, y=2)
+      >>> C(-1)
+      Traceback (most recent call last):
+          ...
+      ValueError: x must be positive
+
+.. autoclass:: attr.Attribute
+
+   Instances of this class are frequently used for introspection purposes like:
+
+   - :func:`fields` returns a tuple of them.
+   - Validators get them passed as the first argument.
+
+   .. warning::
+
+       You should never instantiate this class yourself!
+
+   .. doctest::
+
+      >>> import attr
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      >>> attr.fields(C).x
+      Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
+
+
+.. autofunction:: attr.make_class
+
+   This is handy if you want to programmatically create classes.
+
+   For example:
+
+   .. doctest::
+
+      >>> C1 = attr.make_class("C1", ["x", "y"])
+      >>> C1(1, 2)
+      C1(x=1, y=2)
+      >>> C2 = attr.make_class("C2", {"x": attr.ib(default=42),
+      ...                             "y": attr.ib(default=attr.Factory(list))})
+      >>> C2()
+      C2(x=42, y=[])
+
+
+.. autoclass:: attr.Factory
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(default=attr.Factory(list))
+      ...     y = attr.ib(default=attr.Factory(
+      ...         lambda self: set(self.x),
+      ...         takes_self=True)
+      ...     )
+      >>> C()
+      C(x=[], y=set())
+      >>> C([1, 2, 3])
+      C(x=[1, 2, 3], y={1, 2, 3})
+
+
+.. autoexception:: attr.exceptions.FrozenInstanceError
+.. autoexception:: attr.exceptions.AttrsAttributeNotFoundError
+.. autoexception:: attr.exceptions.NotAnAttrsClassError
+.. autoexception:: attr.exceptions.DefaultAlreadySetError
+.. autoexception:: attr.exceptions.UnannotatedAttributeError
+
+   For example::
+
+       @attr.s(auto_attribs=True)
+       class C:
+           x: int
+           y = attr.ib()  # <- ERROR!
+
+
+.. _helpers:
+
+Helpers
+-------
+
+``attrs`` comes with a bunch of helper methods that make working with it easier:
+
+.. autofunction:: attr.fields
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.fields(C)
+      (Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None))
+      >>> attr.fields(C)[1]
+      Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
+      >>> attr.fields(C).y is attr.fields(C)[1]
+      True
+
+.. autofunction:: attr.fields_dict
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.fields_dict(C)
+      {'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)}
+      >>> attr.fields_dict(C)['y']
+      Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
+      >>> attr.fields_dict(C)['y'] is attr.fields(C).y
+      True
+
+
+.. autofunction:: attr.has
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     pass
+      >>> attr.has(C)
+      True
+      >>> attr.has(object)
+      False
+
+
+.. autofunction:: attr.asdict
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.asdict(C(1, C(2, 3)))
+      {'x': 1, 'y': {'x': 2, 'y': 3}}
+
+
+.. autofunction:: attr.astuple
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> attr.astuple(C(1,2))
+      (1, 2)
+
+``attrs`` includes some handy helpers for filtering:
+
+.. autofunction:: attr.filters.include
+
+.. autofunction:: attr.filters.exclude
+
+See :ref:`asdict` for examples.
+
+.. autofunction:: attr.evolve
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib()
+      ...     y = attr.ib()
+      >>> i1 = C(1, 2)
+      >>> i1
+      C(x=1, y=2)
+      >>> i2 = attr.evolve(i1, y=3)
+      >>> i2
+      C(x=1, y=3)
+      >>> i1 == i2
+      False
+
+   ``evolve`` creates a new instance using ``__init__``.
+   This fact has several implications:
+
+   * private attributes should be specified without the leading underscore, just like in ``__init__``.
+   * attributes with ``init=False`` can't be set with ``evolve``.
+   * the usual ``__init__`` validators will validate the new values.
+
+.. autofunction:: validate
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.instance_of(int))
+      >>> i = C(1)
+      >>> i.x = "1"
+      >>> attr.validate(i)
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got '1' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None), <type 'int'>, '1')
+
+
+Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact:
+
+.. autofunction:: set_run_validators
+
+.. autofunction:: get_run_validators
+
+
+.. _api_validators:
+
+Validators
+----------
+
+``attrs`` comes with some common validators in the ``attrs.validators`` module:
+
+
+.. autofunction:: attr.validators.instance_of
+
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.instance_of(int))
+      >>> C(42)
+      C(x=42)
+      >>> C("42")
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+      >>> C(None)
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got None that is a <type 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None), <type 'int'>, None)
+
+.. autofunction:: attr.validators.in_
+
+   For example:
+
+   .. doctest::
+
+       >>> import enum
+       >>> class State(enum.Enum):
+       ...     ON = "on"
+       ...     OFF = "off"
+       >>> @attr.s
+       ... class C(object):
+       ...     state = attr.ib(validator=attr.validators.in_(State))
+       ...     val = attr.ib(validator=attr.validators.in_([1, 2, 3]))
+       >>> C(State.ON, 1)
+       C(state=<State.ON: 'on'>, val=1)
+       >>> C("on", 1)
+       Traceback (most recent call last):
+          ...
+       ValueError: 'state' must be in <enum 'State'> (got 'on')
+       >>> C(State.ON, 4)
+       Traceback (most recent call last):
+          ...
+       ValueError: 'val' must be in [1, 2, 3] (got 4)
+
+.. autofunction:: attr.validators.provides
+
+.. autofunction:: attr.validators.and_
+
+   For convenience, it's also possible to pass a list to :func:`attr.ib`'s validator argument.
+
+   Thus the following two statements are equivalent::
+
+      x = attr.ib(validator=attr.validators.and_(v1, v2, v3))
+      x = attr.ib(validator=[v1, v2, v3])
+
+.. autofunction:: attr.validators.optional
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(int)))
+      >>> C(42)
+      C(x=42)
+      >>> C("42")
+      Traceback (most recent call last):
+         ...
+      TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+      >>> C(None)
+      C(x=None)
+
+
+Converters
+----------
+
+.. autofunction:: attr.converters.optional
+
+   For example:
+
+   .. doctest::
+
+      >>> @attr.s
+      ... class C(object):
+      ...     x = attr.ib(converter=attr.converters.optional(int))
+      >>> C(None)
+      C(x=None)
+      >>> C(42)
+      C(x=42)
+
+
+Deprecated APIs
+---------------
+
+The serious business aliases used to be called ``attr.attributes`` and ``attr.attr``.
+There are no plans to remove them but they shouldn't be used in new code.
+
+.. autofunction:: assoc
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/backward-compatibility.rst
@@ -0,0 +1,19 @@
+Backward Compatibility
+======================
+
+.. currentmodule:: attr
+
+``attrs`` has a very strong backward compatibility policy that is inspired by the policy of the `Twisted framework <https://twistedmatrix.com/trac/wiki/CompatibilityPolicy>`_.
+
+Put simply, you shouldn't ever be afraid to upgrade ``attrs`` if you're only using its public APIs.
+If there will ever be a need to break compatibility, it will be announced in the :doc:`changelog` and raise a ``DeprecationWarning`` for a year (if possible) before it's finally really broken.
+
+
+.. _exemption:
+
+.. warning::
+
+   The structure of the :class:`attr.Attribute` class is exempt from this rule.
+   It *will* change in the future, but since it should be considered read-only, that shouldn't matter.
+
+   However if you intend to build extensions on top of ``attrs`` you have to anticipate that.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/changelog.rst
@@ -0,0 +1,1 @@
+.. include:: ../CHANGELOG.rst
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/conf.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+import os
+import re
+
+
+def read(*parts):
+    """
+    Build an absolute path from *parts* and and return the contents of the
+    resulting file.  Assume UTF-8 encoding.
+    """
+    here = os.path.abspath(os.path.dirname(__file__))
+    with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f:
+        return f.read()
+
+
+def find_version(*file_paths):
+    """
+    Build a path from *file_paths* and search for a ``__version__``
+    string inside.
+    """
+    version_file = read(*file_paths)
+    version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
+                              version_file, re.M)
+    if version_match:
+        return version_match.group(1)
+    raise RuntimeError("Unable to find version string.")
+
+
+# -- General configuration ------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.todo',
+]
+
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'attrs'
+copyright = u'2015, Hynek Schlawack'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+release = find_version("../src/attr/__init__.py")
+version = release.rsplit(u".", 1)[0]
+# The full version, including alpha/beta/rc tags.
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+
+html_theme = "alabaster"
+html_theme_options = {
+    "font_family": '"Avenir Next", Calibri, "PT Sans", sans-serif',
+    "head_font_family": '"Avenir Next", Calibri, "PT Sans", sans-serif',
+    "font_size": "18px",
+    "page_width": "980px",
+}
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = "_static/attrs_logo.svg"
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If false, no module index is generated.
+html_domain_indices = True
+
+# If false, no index is generated.
+html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'attrsdoc'
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'attrs', u'attrs Documentation',
+     [u'Hynek Schlawack'], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    ('index', 'attrs', u'attrs Documentation',
+     u'Hynek Schlawack', 'attrs', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+intersphinx_mapping = {
+    "https://docs.python.org/3": None,
+}
+
+# Allow non-local URIs so we can have images in CHANGELOG etc.
+suppress_warnings = ['image.nonlocal_uri']
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/contributing.rst
@@ -0,0 +1,5 @@
+.. _contributing:
+
+.. include:: ../.github/CONTRIBUTING.rst
+
+.. include:: ../.github/CODE_OF_CONDUCT.rst
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/docutils.conf
@@ -0,0 +1,3 @@
+[parsers]
+[restructuredtext parser]
+smart_quotes=yes
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/examples.rst
@@ -0,0 +1,601 @@
+.. _examples:
+
+``attrs`` by Example
+====================
+
+
+Basics
+------
+
+The simplest possible usage is:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class Empty(object):
+   ...     pass
+   >>> Empty()
+   Empty()
+   >>> Empty() == Empty()
+   True
+   >>> Empty() is Empty()
+   False
+
+So in other words: ``attrs`` is useful even without actual attributes!
+
+But you'll usually want some data on your classes, so let's add some:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class Coordinates(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+
+By default, all features are added, so you immediately have a fully functional data class with a nice ``repr`` string and comparison methods.
+
+.. doctest::
+
+   >>> c1 = Coordinates(1, 2)
+   >>> c1
+   Coordinates(x=1, y=2)
+   >>> c2 = Coordinates(x=2, y=1)
+   >>> c2
+   Coordinates(x=2, y=1)
+   >>> c1 == c2
+   False
+
+As shown, the generated ``__init__`` method allows for both positional and keyword arguments.
+
+If playful naming turns you off, ``attrs`` comes with serious business aliases:
+
+.. doctest::
+
+   >>> from attr import attrs, attrib
+   >>> @attrs
+   ... class SeriousCoordinates(object):
+   ...     x = attrib()
+   ...     y = attrib()
+   >>> SeriousCoordinates(1, 2)
+   SeriousCoordinates(x=1, y=2)
+   >>> attr.fields(Coordinates) == attr.fields(SeriousCoordinates)
+   True
+
+For private attributes, ``attrs`` will strip the leading underscores for keyword arguments:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     _x = attr.ib()
+   >>> C(x=1)
+   C(_x=1)
+
+If you want to initialize your private attributes yourself, you can do that too:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     _x = attr.ib(init=False, default=42)
+   >>> C()
+   C(_x=42)
+   >>> C(23)
+   Traceback (most recent call last):
+      ...
+   TypeError: __init__() takes exactly 1 argument (2 given)
+
+An additional way of defining attributes is supported too.
+This is useful in times when you want to enhance classes that are not yours (nice ``__repr__`` for Django models anyone?):
+
+.. doctest::
+
+   >>> class SomethingFromSomeoneElse(object):
+   ...     def __init__(self, x):
+   ...         self.x = x
+   >>> SomethingFromSomeoneElse = attr.s(
+   ...     these={
+   ...         "x": attr.ib()
+   ...     }, init=False)(SomethingFromSomeoneElse)
+   >>> SomethingFromSomeoneElse(1)
+   SomethingFromSomeoneElse(x=1)
+
+
+`Subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, but ``attrs`` will still do what you'd hope for:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class A(object):
+   ...     a = attr.ib()
+   ...     def get_a(self):
+   ...         return self.a
+   >>> @attr.s
+   ... class B(object):
+   ...     b = attr.ib()
+   >>> @attr.s
+   ... class C(A, B):
+   ...     c = attr.ib()
+   >>> i = C(1, 2, 3)
+   >>> i
+   C(a=1, b=2, c=3)
+   >>> i == C(1, 2, 3)
+   True
+   >>> i.get_a()
+   1
+
+The order of the attributes is defined by the `MRO <https://www.python.org/download/releases/2.3/mro/>`_.
+
+In Python 3, classes defined within other classes are `detected <https://www.python.org/dev/peps/pep-3155/>`_ and reflected in the ``__repr__``.
+In Python 2 though, it's impossible.
+Therefore ``@attr.s`` comes with the ``repr_ns`` option to set it manually:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     @attr.s(repr_ns="C")
+   ...     class D(object):
+   ...         pass
+   >>> C.D()
+   C.D()
+
+``repr_ns`` works on both Python 2 and 3.
+On Python 3 it overrides the implicit detection.
+
+
+.. _asdict:
+
+Converting to Collections Types
+-------------------------------
+
+When you have a class with data, it often is very convenient to transform that class into a :class:`dict` (for example if you want to serialize it to JSON):
+
+.. doctest::
+
+   >>> attr.asdict(Coordinates(x=1, y=2))
+   {'x': 1, 'y': 2}
+
+Some fields cannot or should not be transformed.
+For that, :func:`attr.asdict` offers a callback that decides whether an attribute should be included:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class UserList(object):
+   ...     users = attr.ib()
+   >>> @attr.s
+   ... class User(object):
+   ...     email = attr.ib()
+   ...     password = attr.ib()
+   >>> attr.asdict(UserList([User("jane@doe.invalid", "s33kred"),
+   ...                       User("joe@doe.invalid", "p4ssw0rd")]),
+   ...             filter=lambda attr, value: attr.name != "password")
+   {'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}
+
+For the common case where you want to :func:`include <attr.filters.include>` or :func:`exclude <attr.filters.exclude>` certain types or attributes, ``attrs`` ships with a few helpers:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class User(object):
+   ...     login = attr.ib()
+   ...     password = attr.ib()
+   ...     id = attr.ib()
+   >>> attr.asdict(
+   ...     User("jane", "s33kred", 42),
+   ...     filter=attr.filters.exclude(attr.fields(User).password, int))
+   {'login': 'jane'}
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   ...     z = attr.ib()
+   >>> attr.asdict(C("foo", "2", 3),
+   ...             filter=attr.filters.include(int, attr.fields(C).x))
+   {'x': 'foo', 'z': 3}
+
+Other times, all you want is a tuple and ``attrs`` won't let you down:
+
+.. doctest::
+
+   >>> import sqlite3
+   >>> import attr
+   >>> @attr.s
+   ... class Foo:
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   >>> foo = Foo(2, 3)
+   >>> with sqlite3.connect(":memory:") as conn:
+   ...    c = conn.cursor()
+   ...    c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS
+   ...    c.execute("INSERT INTO foo VALUES (?, ?)", attr.astuple(foo)) #doctest: +ELLIPSIS
+   ...    foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone())
+   <sqlite3.Cursor object at ...>
+   <sqlite3.Cursor object at ...>
+   >>> foo == foo2
+   True
+
+
+Defaults
+--------
+
+Sometimes you want to have default values for your initializer.
+And sometimes you even want mutable objects as default values (ever used accidentally ``def f(arg=[])``?).
+``attrs`` has you covered in both cases:
+
+.. doctest::
+
+   >>> import collections
+   >>> @attr.s
+   ... class Connection(object):
+   ...     socket = attr.ib()
+   ...     @classmethod
+   ...     def connect(cls, db_string):
+   ...        # ... connect somehow to db_string ...
+   ...        return cls(socket=42)
+   >>> @attr.s
+   ... class ConnectionPool(object):
+   ...     db_string = attr.ib()
+   ...     pool = attr.ib(default=attr.Factory(collections.deque))
+   ...     debug = attr.ib(default=False)
+   ...     def get_connection(self):
+   ...         try:
+   ...             return self.pool.pop()
+   ...         except IndexError:
+   ...             if self.debug:
+   ...                 print("New connection!")
+   ...             return Connection.connect(self.db_string)
+   ...     def free_connection(self, conn):
+   ...         if self.debug:
+   ...             print("Connection returned!")
+   ...         self.pool.appendleft(conn)
+   ...
+   >>> cp = ConnectionPool("postgres://localhost")
+   >>> cp
+   ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False)
+   >>> conn = cp.get_connection()
+   >>> conn
+   Connection(socket=42)
+   >>> cp.free_connection(conn)
+   >>> cp
+   ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False)
+
+More information on why class methods for constructing objects are awesome can be found in this insightful `blog post <http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html>`_.
+
+Default factories can also be set using a decorator.
+The method receives the partially initialized instance which enables you to base a default value on other attributes:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(default=1)
+   ...     y = attr.ib()
+   ...     @y.default
+   ...     def name_does_not_matter(self):
+   ...         return self.x + 1
+   >>> C()
+   C(x=1, y=2)
+
+
+And since the case of ``attr.ib(default=attr.Factory(f))`` is so common, ``attrs`` also comes with syntactic sugar for it:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(factory=list)
+   >>> C()
+   C(x=[])
+
+.. _examples_validators:
+
+Validators
+----------
+
+Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments.
+
+``attrs`` offers two ways to define validators for each attribute and it's up to you to choose which one suites better your style and project.
+
+You can use a decorator:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     @x.validator
+   ...     def check(self, attribute, value):
+   ...         if value > 42:
+   ...             raise ValueError("x must be smaller or equal to 42")
+   >>> C(42)
+   C(x=42)
+   >>> C(43)
+   Traceback (most recent call last):
+      ...
+   ValueError: x must be smaller or equal to 42
+
+ ...or a callable...
+
+.. doctest::
+
+   >>> def x_smaller_than_y(instance, attribute, value):
+   ...     if value >= instance.y:
+   ...         raise ValueError("'x' has to be smaller than 'y'!")
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=[attr.validators.instance_of(int),
+   ...                            x_smaller_than_y])
+   ...     y = attr.ib()
+   >>> C(x=3, y=4)
+   C(x=3, y=4)
+   >>> C(x=4, y=3)
+   Traceback (most recent call last):
+      ...
+   ValueError: 'x' has to be smaller than 'y'!
+
+...or both at once:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=attr.validators.instance_of(int))
+   ...     @x.validator
+   ...     def fits_byte(self, attribute, value):
+   ...         if not 0 <= value < 256:
+   ...             raise ValueError("value out of bounds")
+   >>> C(128)
+   C(x=128)
+   >>> C("128")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=one), <class 'int'>, '128')
+   >>> C(256)
+   Traceback (most recent call last):
+      ...
+   ValueError: value out of bounds
+
+
+``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=attr.validators.instance_of(int))
+   >>> C(42)
+   C(x=42)
+   >>> C("42")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+
+Check out :ref:`validators` for more details.
+
+
+Conversion
+----------
+
+Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
+This can be useful for doing type-conversions on values that you don't want to force your callers to do.
+
+.. doctest::
+
+    >>> @attr.s
+    ... class C(object):
+    ...     x = attr.ib(converter=int)
+    >>> o = C("1")
+    >>> o.x
+    1
+
+Check out :ref:`converters` for more details.
+
+
+.. _metadata:
+
+Metadata
+--------
+
+All ``attrs`` attributes may include arbitrary metadata in the form of a read-only dictionary.
+
+.. doctest::
+
+    >>> @attr.s
+    ... class C(object):
+    ...    x = attr.ib(metadata={'my_metadata': 1})
+    >>> attr.fields(C).x.metadata
+    mappingproxy({'my_metadata': 1})
+    >>> attr.fields(C).x.metadata['my_metadata']
+    1
+
+Metadata is not used by ``attrs``, and is meant to enable rich functionality in third-party libraries.
+The metadata dictionary follows the normal dictionary rules: keys need to be hashable, and both keys and values are recommended to be immutable.
+
+If you're the author of a third-party library with ``attrs`` integration, please see :ref:`Extending Metadata <extending_metadata>`.
+
+
+Types
+-----
+
+``attrs`` also allows you to associate a type with an attribute using either the *type* argument to :func:`attr.ib` or -- as of Python 3.6 -- using `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_-annotations:
+
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C:
+   ...     x = attr.ib(type=int)
+   ...     y: int = attr.ib()
+   >>> attr.fields(C).x.type
+   <class 'int'>
+   >>> attr.fields(C).y.type
+   <class 'int'>
+
+If you don't mind annotating *all* attributes, you can even drop the :func:`attr.ib` and assign default values instead:
+
+.. doctest::
+
+   >>> import typing
+   >>> @attr.s(auto_attribs=True)
+   ... class AutoC:
+   ...     cls_var: typing.ClassVar[int] = 5  # this one is ignored
+   ...     l: typing.List[int] = attr.Factory(list)
+   ...     x: int = 1
+   ...     foo: str = attr.ib(
+   ...          default="every attrib needs a type if auto_attribs=True"
+   ...     )
+   ...     bar: typing.Any = None
+   >>> attr.fields(AutoC).l.type
+   typing.List[int]
+   >>> attr.fields(AutoC).x.type
+   <class 'int'>
+   >>> attr.fields(AutoC).foo.type
+   <class 'str'>
+   >>> attr.fields(AutoC).bar.type
+   typing.Any
+   >>> AutoC()
+   AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None)
+   >>> AutoC.cls_var
+   5
+
+The generated ``__init__`` method will have an attribute called ``__annotations__`` that contains this type information.
+
+.. warning::
+
+   ``attrs`` itself doesn't have any features that work on top of type metadata *yet*.
+   However it's useful for writing your own validators or serialization frameworks.
+
+
+.. _slots:
+
+Slots
+-----
+
+:term:`Slotted classes` have a bunch of advantages on CPython.
+Defining ``__slots__`` by hand is tedious, in ``attrs`` it's just a matter of passing ``slots=True``:
+
+.. doctest::
+
+   >>> @attr.s(slots=True)
+   ... class Coordinates(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+
+
+Immutability
+------------
+
+Sometimes you have instances that shouldn't be changed after instantiation.
+Immutability is especially popular in functional programming and is generally a very good thing.
+If you'd like to enforce it, ``attrs`` will try to help:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class C(object):
+   ...     x = attr.ib()
+   >>> i = C(1)
+   >>> i.x = 2
+   Traceback (most recent call last):
+      ...
+   attr.exceptions.FrozenInstanceError: can't set attribute
+   >>> i.x
+   1
+
+Please note that true immutability is impossible in Python but it will :ref:`get <how-frozen>` you 99% there.
+By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example.
+
+In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes.
+In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/assoc>`_ and ``attrs`` shamelessly imitates it: :func:`attr.evolve`:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   >>> i1 = C(1, 2)
+   >>> i1
+   C(x=1, y=2)
+   >>> i2 = attr.evolve(i1, y=3)
+   >>> i2
+   C(x=1, y=3)
+   >>> i1 == i2
+   False
+
+
+Other Goodies
+-------------
+
+Sometimes you may want to create a class programmatically.
+``attrs`` won't let you down and gives you :func:`attr.make_class` :
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C1(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   >>> C2 = attr.make_class("C2", ["x", "y"])
+   >>> attr.fields(C1) == attr.fields(C2)
+   True
+
+You can still have power over the attributes if you pass a dictionary of name: ``attr.ib`` mappings and can pass arguments to ``@attr.s``:
+
+.. doctest::
+
+   >>> C = attr.make_class("C", {"x": attr.ib(default=42),
+   ...                           "y": attr.ib(default=attr.Factory(list))},
+   ...                     repr=False)
+   >>> i = C()
+   >>> i  # no repr added!
+   <__main__.C object at ...>
+   >>> i.x
+   42
+   >>> i.y
+   []
+
+If you need to dynamically make a class with :func:`attr.make_class` and it needs to be a subclass of something else than ``object``, use the ``bases`` argument:
+
+.. doctest::
+
+  >>> class D(object):
+  ...    def __eq__(self, other):
+  ...        return True  # arbitrary example
+  >>> C = attr.make_class("C", {}, bases=(D,), cmp=False)
+  >>> isinstance(C(), D)
+  True
+
+Sometimes, you want to have your class's ``__init__`` method do more than just
+the initialization, validation, etc. that gets done for you automatically when
+using ``@attr.s``.
+To do this, just define a ``__attrs_post_init__`` method in your class.
+It will get called at the end of the generated ``__init__`` method.
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib()
+   ...     z = attr.ib(init=False)
+   ...
+   ...     def __attrs_post_init__(self):
+   ...         self.z = self.x + self.y
+   >>> obj = C(x=1, y=2)
+   >>> obj
+   C(x=1, y=2, z=3)
+
+Finally, you can exclude single attributes from certain methods:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     user = attr.ib()
+   ...     password = attr.ib(repr=False)
+   >>> C("me", "s3kr3t")
+   C(user='me')
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/extending.rst
@@ -0,0 +1,112 @@
+.. _extending:
+
+Extending
+=========
+
+Each ``attrs``-decorated class has a ``__attrs_attrs__`` class attribute.
+It is a tuple of :class:`attr.Attribute` carrying meta-data about each attribute.
+
+So it is fairly simple to build your own decorators on top of ``attrs``:
+
+.. doctest::
+
+   >>> import attr
+   >>> def print_attrs(cls):
+   ...     print(cls.__attrs_attrs__)
+   >>> @print_attrs
+   ... @attr.s
+   ... class C(object):
+   ...     a = attr.ib()
+   (Attribute(name='a', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None),)
+
+
+.. warning::
+
+   The :func:`attr.s` decorator **must** be applied first because it puts ``__attrs_attrs__`` in place!
+   That means that is has to come *after* your decorator because::
+
+      @a
+      @b
+      def f():
+         pass
+
+   is just `syntactic sugar <https://en.wikipedia.org/wiki/Syntactic_sugar>`_ for::
+
+      def original_f():
+         pass
+
+      f = a(b(original_f))
+
+
+Wrapping the Decorator
+----------------------
+
+A more elegant way can be to wrap ``attrs`` altogether and build a class `DSL <https://en.wikipedia.org/wiki/Domain-specific_language>`_ on top of it.
+
+An example for that is the package `environ_config <https://github.com/hynek/environ_config>`_ that uses ``attrs`` under the hood to define environment-based configurations declaratively without exposing ``attrs`` APIs at all.
+
+
+Types
+-----
+
+``attrs`` offers two ways of attaching type information to attributes:
+
+- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ annotations on Python 3.6 and later,
+- and the *type* argument to :func:`attr.ib`.
+
+This information is available to you:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class C(object):
+   ...     x: int = attr.ib()
+   ...     y = attr.ib(type=str)
+   >>> attr.fields(C).x.type
+   <class 'int'>
+   >>> attr.fields(C).y.type
+   <class 'str'>
+
+Currently, ``attrs`` doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers!
+
+
+.. _extending_metadata:
+
+Metadata
+--------
+
+If you're the author of a third-party library with ``attrs`` integration, you may want to take advantage of attribute metadata.
+
+Here are some tips for effective use of metadata:
+
+- Try making your metadata keys and values immutable.
+  This keeps the entire ``Attribute`` instances immutable too.
+
+- To avoid metadata key collisions, consider exposing your metadata keys from your modules.::
+
+    from mylib import MY_METADATA_KEY
+
+    @attr.s
+    class C(object):
+      x = attr.ib(metadata={MY_METADATA_KEY: 1})
+
+  Metadata should be composable, so consider supporting this approach even if you decide implementing your metadata in one of the following ways.
+
+- Expose ``attr.ib`` wrappers for your specific metadata.
+  This is a more graceful approach if your users don't require metadata from other libraries.
+
+  .. doctest::
+
+    >>> MY_TYPE_METADATA = '__my_type_metadata'
+    >>>
+    >>> def typed(cls, default=attr.NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata={}):
+    ...     metadata = dict() if not metadata else metadata
+    ...     metadata[MY_TYPE_METADATA] = cls
+    ...     return attr.ib(default, validator, repr, cmp, hash, init, convert, metadata)
+    >>>
+    >>> @attr.s
+    ... class C(object):
+    ...     x = typed(int, default=1, init=False)
+    >>> attr.fields(C).x.metadata[MY_TYPE_METADATA]
+    <class 'int'>
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/glossary.rst
@@ -0,0 +1,63 @@
+Glossary
+========
+
+.. glossary::
+
+   dict classes
+      A regular class whose attributes are stored in the ``__dict__`` attribute of every single instance.
+      This is quite wasteful especially for objects with very few data attributes and the space consumption can become significant when creating large numbers of instances.
+
+      This is the type of class you get by default both with and without ``attrs``.
+
+   slotted classes
+      A class that has no ``__dict__`` attribute and `defines <https://docs.python.org/3/reference/datamodel.html#slots>`_ its attributes in a ``__slots__`` attribute instead.
+      In ``attrs``, they are created by passing ``slots=True`` to ``@attr.s``.
+
+      Their main advantage is that they use less memory on CPython [#pypy]_.
+
+      However they also come with a bunch of possibly surprising gotchas:
+
+      - Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies ``__slots__``:
+
+        .. doctest::
+
+          >>> import attr
+          >>> @attr.s(slots=True)
+          ... class Coordinates(object):
+          ...     x = attr.ib()
+          ...     y = attr.ib()
+          ...
+          >>> c = Coordinates(x=1, y=2)
+          >>> c.z = 3
+          Traceback (most recent call last):
+              ...
+          AttributeError: 'Coordinates' object has no attribute 'z'
+
+      - Slotted classes can inherit from other classes just like non-slotted classes, but some of the benefits of slotted classes are lost if you do that.
+        If you must inherit from other classes, try to inherit only from other slot classes.
+
+      - Using :mod:`pickle` with slotted classes requires pickle protocol 2 or greater.
+        Python 2 uses protocol 0 by default so the protocol needs to be specified.
+        Python 3 uses protocol 3 by default.
+        You can support protocol 0 and 1 by implementing :meth:`__getstate__ <object.__getstate__>` and :meth:`__setstate__ <object.__setstate__>` methods yourself.
+        Those methods are created for frozen slotted classes because they won't pickle otherwise.
+        `Think twice <https://www.youtube.com/watch?v=7KnfGDajDQw>`_ before using :mod:`pickle` though.
+
+      - As always with slotted classes, you must specify a ``__weakref__`` slot if you wish for the class to be weak-referenceable.
+        Here's how it looks using ``attrs``:
+
+        .. doctest::
+
+          >>> import weakref
+          >>> @attr.s(slots=True)
+          ... class C(object):
+          ...     __weakref__ = attr.ib(init=False, hash=False, repr=False, cmp=False)
+          ...     x = attr.ib()
+          >>> c = C(1)
+          >>> weakref.ref(c)
+          <weakref at 0x...; to 'C' at 0x...>
+      - Since it's currently impossible to make a class slotted after it's created, ``attrs`` has to replace your class with a new one.
+        While it tries to do that as graciously as possible, certain metaclass features like ``__init_subclass__`` do not work with slotted classes.
+
+
+.. [#pypy] On PyPy, there is no memory advantage in using slotted classes.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/hashing.rst
@@ -0,0 +1,56 @@
+Hashing
+=======
+
+.. warning::
+
+   The overarching theme is to never set the ``@attr.s(hash=X)`` parameter yourself.
+   Leave it at ``None`` which means that ``attrs`` will do the right thing for you, depending on the other parameters:
+
+   - If you want to make objects hashable by value: use ``@attr.s(frozen=True)``.
+   - If you want hashing and comparison by object identity: use ``@attr.s(cmp=False)``
+
+   Setting ``hash`` yourself can have unexpected consequences so we recommend to tinker with it only if you know exactly what you're doing.
+
+Under certain circumstances, it's necessary for objects to be *hashable*.
+For example if you want to put them into a :class:`set` or if you want to use them as keys in a :class:`dict`.
+
+The *hash* of an object is an integer that represents the contents of an object.
+It can be obtained by calling :func:`hash` on an object and is implemented by writing a ``__hash__`` method for your class.
+
+``attrs`` will happily write a ``__hash__`` method you [#fn1]_, however it will *not* do so by default.
+Because according to the definition_ from the official Python docs, the returned hash has to fullfil certain constraints:
+
+#. Two objects that are equal, **must** have the same hash.
+   This means that if ``x == y``, it *must* follow that ``hash(x) == hash(y)``.
+
+   By default, Python classes are compared *and* hashed by their :func:`id`.
+   That means that every instance of a class has a different hash, no matter what attributes it carries.
+
+   It follows that the moment you (or ``attrs``) change the way equality is handled by implementing ``__eq__`` which is based on attribute values, this constraint is broken.
+   For that reason Python 3 will make a class that has customized equality unhashable.
+   Python 2 on the other hand will happily let you shoot your foot off.
+   Unfortunately ``attrs`` currently mimics Python 2's behavior for backward compatibility reasons if you set ``hash=False``.
+
+   The *correct way* to achieve hashing by id is to set ``@attr.s(cmp=False)``.
+   Setting ``@attr.s(hash=False)`` (that implies ``cmp=True``) is almost certainly a *bug*.
+
+#. If two object are not equal, their hash **should** be different.
+
+   While this isn't a requirement from a standpoint of correctness, sets and dicts become less effective if there are a lot of identical hashes.
+   The worst case is when all objects have the same hash which turns a set into a list.
+
+#. The hash of an object **must not** change.
+
+   If you create a class with ``@attr.s(frozen=True)`` this is fullfilled by definition, therefore ``attrs`` will write a ``__hash__`` function for you automatically.
+   You can also force it to write one with ``hash=True`` but then it's *your* responsibility to make sure that the object is not mutated.
+
+   This point is the reason why mutable structures like lists, dictionaries, or sets aren't hashable while immutable ones like tuples or frozensets are:
+   point 1 and 2 require that the hash changes with the contents but point 3 forbids it.
+
+For a more thorough explanation of this topic, please refer to this blog post: `Python Hashes and Equality`_.
+
+
+.. [#fn1] The hash is computed by hashing a tuple that consists of an unique id for the class plus all attribute values.
+
+.. _definition: https://docs.python.org/3/glossary.html#term-hashable
+.. _`Python Hashes and Equality`: https://hynek.me/articles/hashes-and-equality/
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/how-does-it-work.rst
@@ -0,0 +1,100 @@
+.. _how:
+
+How Does It Work?
+=================
+
+
+Boilerplate
+-----------
+
+``attrs`` certainly isn't the first library that aims to simplify class definition in Python.
+But its **declarative** approach combined with **no runtime overhead** lets it stand out.
+
+Once you apply the ``@attr.s`` decorator to a class, ``attrs`` searches the class object for instances of ``attr.ib``\ s.
+Internally they're a representation of the data passed into ``attr.ib`` along with a counter to preserve the order of the attributes.
+
+In order to ensure that sub-classing works as you'd expect it to work, ``attrs`` also walks the class hierarchy and collects the attributes of all super-classes.
+Please note that ``attrs`` does *not* call ``super()`` *ever*.
+It will write dunder methods to work on *all* of those attributes which also has performance benefits due to fewer function calls.
+
+Once ``attrs`` knows what attributes it has to work on, it writes the requested dunder methods and -- depending on whether you wish to have a :term:`dict <dict classes>` or :term:`slotted <slotted classes>` class -- creates a new class for you (``slots=True``) or attaches them to the original class (``slots=False``).
+While creating new classes is more elegant, we've run into several edge cases surrounding metaclasses that make it impossible to go this route unconditionally.
+
+To be very clear: if you define a class with a single attribute  without a default value, the generated ``__init__`` will look *exactly* how you'd expect:
+
+.. doctest::
+
+   >>> import attr, inspect
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   >>> print(inspect.getsource(C.__init__))
+   def __init__(self, x):
+       self.x = x
+   <BLANKLINE>
+
+No magic, no meta programming, no expensive introspection at runtime.
+
+****
+
+Everything until this point happens exactly *once* when the class is defined.
+As soon as a class is done, it's done.
+And it's just a regular Python class like any other, except for a single ``__attrs_attrs__`` attribute that can be used for introspection or for writing your own tools and decorators on top of ``attrs`` (like :func:`attr.asdict`).
+
+And once you start instantiating your classes, ``attrs`` is out of your way completely.
+
+This **static** approach was very much a design goal of ``attrs`` and what I strongly believe makes it distinct.
+
+
+.. _how-frozen:
+
+Immutability
+------------
+
+In order to give you immutability, ``attrs`` will attach a ``__setattr__`` method to your class that raises a :exc:`attr.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute.
+
+Depending on whether of not a class is a dict class or a slots class, ``attrs`` uses a different technique to circumvent that limitation in the ``__init__`` method.
+
+Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes.
+
+
+Dict Classes
+++++++++++++
+
+Dict classes -- i.e. regular classes -- simply assign the value directly into the class' eponymous ``__dict__`` (and there's nothing we can do to stop the user to do the same).
+
+The performance impact is negligible.
+
+
+Slots Classes
++++++++++++++
+
+Slots classes are more complicated.
+Here it uses (an aggressively cached) :meth:`object.__setattr__` to set your attributes.
+This is (still) slower than a plain assignment:
+
+.. code-block:: none
+
+  $ pyperf timeit --rigorous \
+        -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \
+        "C(1, 2, 3)"
+  ........................................
+  Median +- std dev: 378 ns +- 12 ns
+
+  $ pyperf timeit --rigorous \
+        -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \
+        "C(1, 2, 3)"
+  ........................................
+  Median +- std dev: 676 ns +- 16 ns
+
+So on a standard notebook the difference is about 300 nanoseconds (1 second is 1,000,000,000 nanoseconds).
+It's certainly something you'll feel in a hot loop but shouldn't matter in normal code.
+Pick what's more important to you.
+
+
+Summary
++++++++
+
+You should avoid to instantiate lots of frozen slotted classes (i.e. ``@attr.s(slots=True, frozen=True)``) in performance-critical code.
+
+Frozen dict classes have barely a performance impact, unfrozen slotted classes are even *faster* than unfrozen dict classes (i.e. regular classes).
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/index.rst
@@ -0,0 +1,89 @@
+======================================
+``attrs``: Classes Without Boilerplate
+======================================
+
+Release v\ |release| (:doc:`What's new? <changelog>`).
+
+.. include:: ../README.rst
+   :start-after: teaser-begin
+   :end-before: -spiel-end-
+
+
+Getting Started
+===============
+
+``attrs`` is a Python-only package `hosted on PyPI <https://pypi.org/project/attrs/>`_.
+The recommended installation method is `pip <https://pip.pypa.io/en/stable/>`_-installing into a `virtualenv <https://hynek.me/articles/virtualenv-lives/>`_:
+
+.. code-block:: console
+
+   $ pip install attrs
+
+The next three steps should bring you up and running in no time:
+
+- :doc:`overview` will show you a simple example of ``attrs`` in action and introduce you to its philosophy.
+  Afterwards, you can start writing your own classes, understand what drives ``attrs``'s design, and know what ``@attr.s`` and ``attr.ib()`` stand for.
+- :doc:`examples` will give you a comprehensive tour of ``attrs``'s features.
+  After reading, you will know about our advanced features and how to use them.
+- Finally :doc:`why` gives you a rundown of potential alternatives and why we think ``attrs`` is superior.
+  Yes, we've heard about ``namedtuple``\ s!
+- If at any point you get confused by some terminology, please check out our :doc:`glossary`.
+
+
+If you need any help while getting started, feel free to use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ and someone will surely help you out!
+
+
+Day-to-Day Usage
+================
+
+- Once you're comfortable with the concepts, our :doc:`api` contains all information you need to use ``attrs`` to its fullest.
+- Instance initialization is one of ``attrs`` key feature areas.
+  Our goal is to relieve you from writing as much code as possible.
+  :doc:`init` gives you an overview what ``attrs`` has to offer and explains some related philosophies we believe in.
+- If you want to put objects into sets or use them as keys in dictionaries, they have to be hashable.
+  The simplest way to do that is to use frozen classes, but the topic is more complex than it seems and :doc:`hashing` will give you a primer on what to look out for.
+- ``attrs`` is built for extension from the ground up.
+  :doc:`extending` will show you the affordances it offers and how to make it a building block of your own projects.
+
+
+.. include:: ../README.rst
+   :start-after: -testimonials-
+   :end-before: -end-
+
+.. include:: ../README.rst
+   :start-after: -project-information-
+
+.. toctree::
+   :maxdepth: 1
+
+   license
+   backward-compatibility
+   contributing
+   changelog
+
+
+----
+
+
+Full Table of Contents
+======================
+
+.. toctree::
+   :maxdepth: 2
+
+   overview
+   why
+   examples
+   init
+   hashing
+   api
+   extending
+   how-does-it-work
+   glossary
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/init.rst
@@ -0,0 +1,354 @@
+Initialization
+==============
+
+In Python, instance intialization happens in the ``__init__`` method.
+Generally speaking, you should keep as little logic as possible in it, and you should think about what the class needs and not how it is going to be instantiated.
+
+Passing complex objects into ``__init__`` and then using them to derive data for the class unnecessarily couples your new class with the old class which makes it harder to test and also will cause problems later.
+
+So assuming you use an ORM and want to extract 2D points from a row object, do not write code like this::
+
+    class Point(object):
+        def __init__(self, database_row):
+            self.x = database_row.x
+            self.y = database_row.y
+
+    pt = Point(row)
+
+Instead, write a :func:`classmethod` that will extract it for you::
+
+   @attr.s
+   class Point(object):
+       x = attr.ib()
+       y = attr.ib()
+
+       @classmethod
+       def from_row(cls, row):
+           return cls(row.x, row.y)
+
+   pt = Point.from_row(row)
+
+Now you can instantiate ``Point``\ s without creating fake row objects in your tests and you can have as many smart creation helpers as you want, in case more data sources appear.
+
+For similar reasons, we strongly discourage from patterns like::
+
+   pt = Point(**row.attributes)
+
+which couples your classes to the data model.
+Try to design your classes in a way that is clean and convenient to use -- not based on your database format.
+The database format can change anytime and you're stuck with a bad class design that is hard to change.
+Embrace classmethods as a filter between reality and what's best for you to work with.
+
+If you look for object serialization, there's a bunch of projects listed on our ``attrs`` extensions `Wiki page`_.
+Some of them even support nested schemas.
+
+
+Private Attributes
+------------------
+
+One thing people tend to find confusing is the treatment of private attributes that start with an underscore.
+``attrs`` follows the doctrine that `there is no such thing as a private argument`_ and strips the underscores from the name when writing the ``__init__`` method signature:
+
+.. doctest::
+
+   >>> import inspect, attr
+   >>> @attr.s
+   ... class C(object):
+   ...    _x = attr.ib()
+   >>> inspect.signature(C.__init__)
+   <Signature (self, x) -> None>
+
+There really isn't a right or wrong, it's a matter of taste.
+But it's important to be aware of it because it can lead to surprising syntax errors:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...    _1 = attr.ib()
+   Traceback (most recent call last):
+      ...
+   SyntaxError: invalid syntax
+
+In this case a valid attribute name ``_1`` got transformed into an invalid argument name ``1``.
+
+
+Defaults
+--------
+
+Sometimes you don't want to pass all attribute values to a class.
+And sometimes, certain attributes aren't even intended to be passed but you want to allow for customization anyways for easier testing.
+
+This is when default values come into play:
+
+.. doctest::
+
+   >>> import attr
+   >>> @attr.s
+   ... class C(object):
+   ...     a = attr.ib(default=42)
+   ...     b = attr.ib(default=attr.Factory(list))
+   ...     c = attr.ib(factory=list)  # syntactic sugar for above
+   ...     d = attr.ib()
+   ...     @d.default
+   ...     def _any_name_except_a_name_of_an_attribute(self):
+   ...        return {}
+   >>> C()
+   C(a=42, b=[], c=[], d={})
+
+It's important that the decorated method -- or any other method or property! -- doesn't have the same name as the attribute, otherwise it would overwrite the attribute definition.
+
+
+Please note that as with function and method signatures, ``default=[]`` will *not* do what you may think it might do:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(default=[])
+   >>> i = C()
+   >>> j = C()
+   >>> i.x.append(42)
+   >>> j.x
+   [42]
+
+
+This is why ``attrs`` comes with factory options.
+
+.. warning::
+
+   Please note that the decorator based defaults have one gotcha:
+   they are executed when the attribute is set, that means depending on the order of attributes, the ``self`` object may not be fully initialized when they're called.
+
+   Therefore you should use ``self`` as little as possible.
+
+   Even the smartest of us can `get confused`_ by what happens if you pass partially initialized objects around.
+
+
+ .. _validators:
+
+Validators
+----------
+
+Another thing that definitely *does* belong into ``__init__`` is checking the resulting instance for invariants.
+This is why ``attrs`` has the concept of validators.
+
+
+Decorator
+~~~~~~~~~
+
+The most straightforward way is using the attribute's ``validator`` method as a decorator.
+
+The method has to accept three arguments:
+
+#. the *instance* that's being validated (aka ``self``),
+#. the *attribute* that it's validating, and finally
+#. the *value* that is passed for it.
+
+If the value does not pass the validator's standards, it just raises an appropriate exception.
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     @x.validator
+   ...     def _check_x(self, attribute, value):
+   ...         if value > 42:
+   ...             raise ValueError("x must be smaller or equal to 42")
+   >>> C(42)
+   C(x=42)
+   >>> C(43)
+   Traceback (most recent call last):
+      ...
+   ValueError: x must be smaller or equal to 42
+
+Again, it's important that the decorated method doesn't have the same name as the attribute.
+
+
+Callables
+~~~~~~~~~
+
+If you want to re-use your validators, you should have a look at the ``validator`` argument to :func:`attr.ib()`.
+
+It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.
+
+Since the validators runs *after* the instance is initialized, you can refer to other attributes while validating:
+
+.. doctest::
+
+   >>> def x_smaller_than_y(instance, attribute, value):
+   ...     if value >= instance.y:
+   ...         raise ValueError("'x' has to be smaller than 'y'!")
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=[attr.validators.instance_of(int),
+   ...                            x_smaller_than_y])
+   ...     y = attr.ib()
+   >>> C(x=3, y=4)
+   C(x=3, y=4)
+   >>> C(x=4, y=3)
+   Traceback (most recent call last):
+      ...
+   ValueError: 'x' has to be smaller than 'y'!
+
+This example also shows of some syntactic sugar for using the :func:`attr.validators.and_` validator: if you pass a list, all validators have to pass.
+
+``attrs`` won't intercept your changes to those attributes but you can always call :func:`attr.validate` on any instance to verify that it's still valid:
+
+.. doctest::
+
+   >>> i = C(4, 5)
+   >>> i.x = 5  # works, no magic here
+   >>> attr.validate(i)
+   Traceback (most recent call last):
+      ...
+   ValueError: 'x' has to be smaller than 'y'!
+
+``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=attr.validators.instance_of(int))
+   >>> C(42)
+   C(x=42)
+   >>> C("42")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
+
+Of course you can mix and match the two approaches at your convenience.
+If you define validators both ways for an attribute, they are both ran:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib(validator=attr.validators.instance_of(int))
+   ...     @x.validator
+   ...     def fits_byte(self, attribute, value):
+   ...         if not 0 <= value < 256:
+   ...             raise ValueError("value out of bounds")
+   >>> C(128)
+   C(x=128)
+   >>> C("128")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=one), <class 'int'>, '128')
+   >>> C(256)
+   Traceback (most recent call last):
+      ...
+   ValueError: value out of bounds
+
+And finally you can disable validators globally:
+
+   >>> attr.set_run_validators(False)
+   >>> C("128")
+   C(x='128')
+   >>> attr.set_run_validators(True)
+   >>> C("128")
+   Traceback (most recent call last):
+      ...
+   TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), <class 'int'>, '128')
+
+
+.. _converters:
+
+Converters
+----------
+
+Finally, sometimes you may want to normalize the values coming in.
+For that ``attrs`` comes with converters.
+
+Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
+This can be useful for doing type-conversions on values that you don't want to force your callers to do.
+
+.. doctest::
+
+    >>> @attr.s
+    ... class C(object):
+    ...     x = attr.ib(converter=int)
+    >>> o = C("1")
+    >>> o.x
+    1
+
+Converters are run *before* validators, so you can use validators to check the final form of the value.
+
+.. doctest::
+
+    >>> def validate_x(instance, attribute, value):
+    ...     if value < 0:
+    ...         raise ValueError("x must be at least 0.")
+    >>> @attr.s
+    ... class C(object):
+    ...     x = attr.ib(converter=int, validator=validate_x)
+    >>> o = C("0")
+    >>> o.x
+    0
+    >>> C("-1")
+    Traceback (most recent call last):
+        ...
+    ValueError: x must be at least 0.
+
+
+Arguably, you can abuse converters as one-argument validators:
+
+.. doctest::
+
+   >>> C("x")
+   Traceback (most recent call last):
+       ...
+   ValueError: invalid literal for int() with base 10: 'x'
+
+
+Post-Init Hook
+--------------
+
+Generally speaking, the moment you think that you need finer control over how your class is instantiated than what ``attrs`` offers, it's usually best to use a classmethod factory or to apply the `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern>`_.
+
+However, sometimes you need to do that one quick thing after your class is initialized.
+And for that ``attrs`` offers the ``__attrs_post_init__`` hook that is automatically detected and run after ``attrs`` is done initializing your instance:
+
+.. doctest::
+
+   >>> @attr.s
+   ... class C(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib(init=False)
+   ...     def __attrs_post_init__(self):
+   ...         self.y = self.x + 1
+   >>> C(1)
+   C(x=1, y=2)
+
+Please note that you can't directly set attributes on frozen classes:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class FrozenBroken(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib(init=False)
+   ...     def __attrs_post_init__(self):
+   ...         self.y = self.x + 1
+   >>> FrozenBroken(1)
+   Traceback (most recent call last):
+      ...
+   attr.exceptions.FrozenInstanceError: can't set attribute
+
+If you need to set attributes on a frozen class, you'll have to resort to the :ref:`same trick <how-frozen>` as ``attrs`` and use :meth:`object.__setattr__`:
+
+.. doctest::
+
+   >>> @attr.s(frozen=True)
+   ... class Frozen(object):
+   ...     x = attr.ib()
+   ...     y = attr.ib(init=False)
+   ...     def __attrs_post_init__(self):
+   ...         object.__setattr__(self, "y", self.x + 1)
+   >>> Frozen(1)
+   Frozen(x=1, y=2)
+
+
+.. _`Wiki page`: https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs
+.. _`get confused`: https://github.com/python-attrs/attrs/issues/289
+.. _`there is no such thing as a private argument`: https://github.com/hynek/characteristic/issues/6
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/license.rst
@@ -0,0 +1,8 @@
+===================
+License and Credits
+===================
+
+``attrs`` is licensed under the `MIT <https://choosealicense.com/licenses/mit/>`_ license.
+The full license text can be also found in the `source code repository <https://github.com/python-attrs/attrs/blob/master/LICENSE>`_.
+
+.. include:: ../AUTHORS.rst
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/overview.rst
@@ -0,0 +1,87 @@
+========
+Overview
+========
+
+In order to fulfill its ambitious goal of bringing back the joy to writing classes, it gives you a class decorator and a way to declaratively define the attributes on that class:
+
+.. include:: ../README.rst
+   :start-after: -code-begin-
+   :end-before: -testimonials-
+
+
+.. _philosophy:
+
+Philosophy
+==========
+
+**It's about regular classes.**
+   ``attrs`` is for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class.
+   It can be used for data-only containers like ``namedtuple``\ s or ``types.SimpleNamespace`` but they're just a sub-genre of what ``attrs`` is good for.
+
+**The class belongs to the users.**
+   You define a class and ``attrs`` adds static methods to that class based on the attributes you declare.
+   The end.
+   It doesn't add metaclasses.
+   It doesn't add classes you've never heard of to your inheritance tree.
+   An ``attrs`` class in runtime is indistiguishable from a regular class: because it *is* a regular class with a few boilerplate-y methods attached.
+
+**Be light on API impact.**
+   As convenient as it seems at first, ``attrs`` will *not* tack on any methods to your classes save the dunder ones.
+   Hence all the useful :ref:`tools <helpers>` that come with ``attrs`` live in functions that operate on top of instances.
+   Since they take an ``attrs`` instance as their first argument, you can attach them to your classes with one line of code.
+
+**Performance matters.**
+   ``attrs`` runtime impact is very close to zero because all the work is done when the class is defined.
+   Once you're instantiating it, ``attrs`` is out of the picture completely.
+
+**No surprises.**
+   ``attrs`` creates classes that arguably work the way a Python beginner would reasonably expect them to work.
+   It doesn't try to guess what you mean because explicit is better than implicit.
+   It doesn't try to be clever because software shouldn't be clever.
+
+Check out :doc:`how-does-it-work` if you'd like to know how it achieves all of the above.
+
+
+What ``attrs`` Is Not
+=====================
+
+``attrs`` does *not* invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies.
+
+All ``attrs`` does is:
+
+1. take your declaration,
+2. write dunder methods based on that information,
+3. and attach them to your class.
+
+It does *nothing* dynamic at runtime, hence zero runtime overhead.
+It's still *your* class.
+Do with it as you please.
+
+
+On the ``attr.s`` and ``attr.ib`` Names
+=======================================
+
+The ``attr.s`` decorator and the ``attr.ib`` function aren't any obscure abbreviations.
+They are a *concise* and highly *readable* way to write ``attrs`` and ``attrib`` with an *explicit namespace*.
+
+At first, some people have a negative gut reaction to that; resembling the reactions to Python's significant whitespace.
+And as with that, once one gets used to it, the readability and explicitness of that API prevails and delights.
+
+For those who can't swallow that API at all, ``attrs`` comes with serious business aliases: ``attr.attrs`` and ``attr.attrib``.
+
+Therefore, the following class definition is identical to the previous one:
+
+.. doctest::
+
+   >>> from attr import attrs, attrib, Factory
+   >>> @attrs
+   ... class SomeClass(object):
+   ...     a_number = attrib(default=42)
+   ...     list_of_numbers = attrib(default=Factory(list))
+   ...
+   ...     def hard_math(self, another_number):
+   ...         return self.a_number + sum(self.list_of_numbers) * another_number
+   >>> SomeClass(1, [1, 2, 3])
+   SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
+
+Use whichever variant fits your taste better.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/docs/why.rst
@@ -0,0 +1,271 @@
+.. _why:
+
+Why not…
+========
+
+
+If you'd like third party's account why ``attrs`` is great, have a look at Glyph's `The One Python Library Everyone Needs <https://glyph.twistedmatrix.com/2016/08/attrs.html>`_!
+
+
+…tuples?
+--------
+
+
+Readability
+^^^^^^^^^^^
+
+What makes more sense while debugging::
+
+   Point(x=1, y=2)
+
+or::
+
+   (1, 2)
+
+?
+
+Let's add even more ambiguity::
+
+   Customer(id=42, reseller=23, first_name="Jane", last_name="John")
+
+or::
+
+   (42, 23, "Jane", "John")
+
+?
+
+Why would you want to write ``customer[2]`` instead of ``customer.first_name``?
+
+Don't get me started when you add nesting.
+If you've never run into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter than yours truly.
+
+Using proper classes with names and types makes program code much more readable and comprehensible_.
+Especially when trying to grok a new piece of software or returning to old code after several months.
+
+.. _comprehensible: https://arxiv.org/pdf/1304.5257.pdf
+
+
+Extendability
+^^^^^^^^^^^^^
+
+Imagine you have a function that takes or returns a tuple.
+Especially if you use tuple unpacking (eg. ``x, y = get_point()``), adding additional data means that you have to change the invocation of that function *everywhere*.
+
+Adding an attribute to a class concerns only those who actually care about that attribute.
+
+
+…namedtuples?
+-------------
+
+:func:`collections.namedtuple`\ s are tuples with names, not classes. [#history]_
+Since writing classes is tiresome in Python, every now and then someone discovers all the typing they could save and gets really excited.
+However that convenience comes at a price.
+
+The most obvious difference between ``namedtuple``\ s and ``attrs``-based classes is that the latter are type-sensitive:
+
+.. doctest::
+
+   >>> import attr
+   >>> C1 = attr.make_class("C1", ["a"])
+   >>> C2 = attr.make_class("C2", ["a"])
+   >>> i1 = C1(1)
+   >>> i2 = C2(1)
+   >>> i1.a == i2.a
+   True
+   >>> i1 == i2
+   False
+
+…while a ``namedtuple`` is *intentionally* `behaving like a tuple`_ which means the type of a tuple is *ignored*:
+
+.. doctest::
+
+   >>> from collections import namedtuple
+   >>> NT1 = namedtuple("NT1", "a")
+   >>> NT2 = namedtuple("NT2", "b")
+   >>> t1 = NT1(1)
+   >>> t2 = NT2(1)
+   >>> t1 == t2 == (1,)
+   True
+
+Other often surprising behaviors include:
+
+- Since they are a subclass of tuples, ``namedtuple``\ s have a length and are both iterable and indexable.
+  That's not what you'd expect from a class and is likely to shadow subtle typo bugs.
+- Iterability also implies that it's easy to accidentally unpack a ``namedtuple`` which leads to hard-to-find bugs. [#iter]_
+- ``namedtuple``\ s have their methods *on your instances* whether you like it or not. [#pollution]_
+- ``namedtuple``\ s are *always* immutable.
+  Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation?  default values?), you have to implement :meth:`__new__() <object.__new__>` which is a particularly hacky and error-prone requirement for a very common problem. [#immutable]_
+- To attach methods to a ``namedtuple`` you have to subclass it.
+  And if you follow the standard library documentation's recommendation of::
+
+    class Point(namedtuple('Point', ['x', 'y'])):
+        # ...
+
+  you end up with a class that has *two* ``Point``\ s in its :attr:`__mro__ <class.__mro__>`: ``[<class 'point.Point'>, <class 'point.Point'>, <type 'tuple'>, <type 'object'>]``.
+
+  That's not only confusing, it also has very practical consequences:
+  for example if you create documentation that includes class hierarchies like `Sphinx's autodoc <http://www.sphinx-doc.org/en/stable/ext/autodoc.html>`_ with ``show-inheritance``.
+  Again: common problem, hacky solution with confusing fallout.
+
+All these things make ``namedtuple``\ s a particularly poor choice for public APIs because all your objects are irrevocably tainted.
+With ``attrs`` your users won't notice a difference because it creates regular, well-behaved classes.
+
+.. admonition:: Summary
+
+  If you want a *tuple with names*, by all means: go for a ``namedtuple``. [#perf]_
+  But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand.
+
+  Other than that, ``attrs`` also adds nifty features like validators, converters, and (mutable!) default values.
+
+
+.. rubric:: Footnotes
+
+.. [#history] The word is that ``namedtuple``\ s were added to the Python standard library as a way to make tuples in return values more readable.
+              And indeed that is something you see throughout the standard library.
+
+              Looking at what the makers of ``namedtuple``\ s use it for themselves is a good guideline for deciding on your own use cases.
+.. [#pollution] ``attrs`` only adds a single attribute: ``__attrs_attrs__`` for introspection.
+                All helpers are functions in the ``attr`` package.
+                Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice.
+.. [#iter] :func:`attr.astuple` can be used to get that behavior in ``attrs`` on *explicit demand*.
+.. [#immutable] ``attrs`` offers *optional* immutability through the ``frozen`` keyword.
+.. [#perf] Although ``attrs`` would serve you just as well!
+           Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases ``attrs`` is even faster if you use ``slots=True`` (which is generally a good idea anyway).
+
+.. _behaving like a tuple: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+
+
+…Data Classes?
+--------------
+
+`PEP 557 <https://www.python.org/dev/peps/pep-0557/>`_ added Data Classes to `Python 3.7 <https://docs.python.org/3.7/whatsnew/3.7.html#pep-557-data-classes>`_ that resemble ``attrs`` in many ways.
+
+They are the result of the Python community's `wish <https://mail.python.org/pipermail/python-ideas/2017-May/045618.html>`_ to have an easier way to write classes in the standard library that doesn't carry the problems of ``namedtuple``\ s.
+To that end, ``attrs`` and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it's a fine library and if it stops you from abusing ``namedtuple``\ s, they are a huge win.
+
+Nevertheless, there are still reasons to prefer ``attrs`` over Data Classes whose relevancy depends on your circumstances:
+
+- ``attrs`` supports all maintream Python versions, including CPython 2.7 and PyPy.
+- Data Classes are intentionally less powerful than ``attrs``.
+  There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, and ``__slots__``, it permeates throughout all APIs.
+
+  On the other hand, Data Classes currently do not offer any significant feature that ``attrs`` doesn't already have.
+- ``attrs`` can and will move faster.
+  We are not bound to any release schedules and we have a clear deprecation policy.
+
+  One of the `reasons <https://www.python.org/dev/peps/pep-0557/#why-not-just-use-attrs>`_ to not vendor ``attrs`` in the standard library was to not impede ``attrs``'s future developement.
+
+
+
+…dicts?
+-------
+
+Dictionaries are not for fixed fields.
+
+If you have a dict, it maps something to something else.
+You should be able to add and remove values.
+
+``attrs`` lets you be specific about those expectations; a dictionary does not.
+It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class.
+
+In other words: if your dict has a fixed and known set of keys, it is an object, not a hash.
+So if you never iterate over the keys of a dict, you should use a proper class.
+
+
+…hand-written classes?
+----------------------
+
+While we're fans of all things artisanal, writing the same nine methods again and again doesn't qualify.
+I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested.
+
+To bring it into perspective, the equivalent of
+
+.. doctest::
+
+   >>> @attr.s
+   ... class SmartClass(object):
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   >>> SmartClass(1, 2)
+   SmartClass(a=1, b=2)
+
+is roughly
+
+.. doctest::
+
+   >>> class ArtisanalClass(object):
+   ...     def __init__(self, a, b):
+   ...         self.a = a
+   ...         self.b = b
+   ...
+   ...     def __repr__(self):
+   ...         return "ArtisanalClass(a={}, b={})".format(self.a, self.b)
+   ...
+   ...     def __eq__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) == (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __ne__(self, other):
+   ...         result = self.__eq__(other)
+   ...         if result is NotImplemented:
+   ...             return NotImplemented
+   ...         else:
+   ...             return not result
+   ...
+   ...     def __lt__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) < (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __le__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) <= (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __gt__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) > (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __ge__(self, other):
+   ...         if other.__class__ is self.__class__:
+   ...             return (self.a, self.b) >= (other.a, other.b)
+   ...         else:
+   ...             return NotImplemented
+   ...
+   ...     def __hash__(self):
+   ...         return hash((self.__class__, self.a, self.b))
+   >>> ArtisanalClass(a=1, b=2)
+   ArtisanalClass(a=1, b=2)
+
+which is quite a mouthful and it doesn't even use any of ``attrs``'s more advanced features like validators or defaults values.
+Also: no tests whatsoever.
+And who will guarantee you, that you don't accidentally flip the ``<`` in your tenth implementation of ``__gt__``?
+
+It also should be noted that ``attrs`` is not an all-or-nothing solution.
+You can freely choose which features you want and disable those that you want more control over:
+
+.. doctest::
+
+   >>> @attr.s(repr=False)
+   ... class SmartClass(object):
+   ...    a = attr.ib()
+   ...    b = attr.ib()
+   ...
+   ...    def __repr__(self):
+   ...        return "<SmartClass(a=%d)>" % (self.a,)
+   >>> SmartClass(1, 2)
+   <SmartClass(a=1)>
+
+.. admonition:: Summary
+
+   If you don't care and like typing, we're not gonna stop you.
+
+   However it takes a lot of bias and determined rationalization to claim that ``attrs`` raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes.
+
+   In any case, if you ever get sick of the repetitiveness and drowning important code in a sea of boilerplate, ``attrs`` will be waiting for you.
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/moz.yaml
@@ -0,0 +1,12 @@
+---
+schema: 1
+description: Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols.
+url: "http://www.attrs.org/"
+release: "18.1.0"
+licenses:
+    - MIT
+bugzilla:
+    product: Taskcluster
+    component: Task Configuration
+vendoring:
+    url: https://pypi.org/packages/source/a/attrs/attrs-18.1.0.tar.gz
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/pyproject.toml
@@ -0,0 +1,27 @@
+[tool.towncrier]
+    package = "attr"
+    package_dir = "src"
+    filename = "CHANGELOG.rst"
+    template = "changelog.d/towncrier_template.rst"
+    issue_format = "`#{issue} <https://github.com/python-attrs/attrs/issues/{issue}>`_"
+    directory = "changelog.d"
+    title_format = "{version} ({project_date})"
+    underlines = ["-", "^"]
+
+    [[tool.towncrier.section]]
+        path = ""
+
+    [[tool.towncrier.type]]
+        directory = "breaking"
+        name = "Backward-incompatible Changes"
+        showcontent = true
+
+    [[tool.towncrier.type]]
+        directory = "deprecation"
+        name = "Deprecations"
+        showcontent = true
+
+    [[tool.towncrier.type]]
+        directory = "change"
+        name = "Changes"
+        showcontent = true
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/setup.cfg
@@ -0,0 +1,26 @@
+[bdist_wheel]
+universal = 1
+
+[metadata]
+license_file = LICENSE
+
+[tool:pytest]
+minversion = 3.0
+strict = true
+addopts = -ra
+testpaths = tests
+filterwarnings = 
+	once::Warning
+
+[isort]
+atomic = true
+lines_after_imports = 2
+lines_between_types = 1
+multi_line_output = 5
+not_skip = __init__.py
+known_first_party = attr
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/setup.py
@@ -0,0 +1,112 @@
+import codecs
+import os
+import re
+
+from setuptools import find_packages, setup
+
+
+###############################################################################
+
+NAME = "attrs"
+PACKAGES = find_packages(where="src")
+META_PATH = os.path.join("src", "attr", "__init__.py")
+KEYWORDS = ["class", "attribute", "boilerplate"]
+CLASSIFIERS = [
+    "Development Status :: 5 - Production/Stable",
+    "Intended Audience :: Developers",
+    "Natural Language :: English",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 2",
+    "Programming Language :: Python :: 2.7",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.4",
+    "Programming Language :: Python :: 3.5",
+    "Programming Language :: Python :: 3.6",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: Implementation :: CPython",
+    "Programming Language :: Python :: Implementation :: PyPy",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+]
+INSTALL_REQUIRES = []
+EXTRAS_REQUIRE = {
+    "docs": [
+        "sphinx",
+        "zope.interface",
+    ],
+    "tests": [
+        "coverage",
+        "hypothesis",
+        "pympler",
+        "pytest",
+        "six",
+        "zope.interface",
+    ],
+}
+EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["docs"]
+
+###############################################################################
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+
+def read(*parts):
+    """
+    Build an absolute path from *parts* and and return the contents of the
+    resulting file.  Assume UTF-8 encoding.
+    """
+    with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f:
+        return f.read()
+
+
+META_FILE = read(META_PATH)
+
+
+def find_meta(meta):
+    """
+    Extract __*meta*__ from META_FILE.
+    """
+    meta_match = re.search(
+        r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta),
+        META_FILE, re.M
+    )
+    if meta_match:
+        return meta_match.group(1)
+    raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta))
+
+
+VERSION = find_meta("version")
+URI = find_meta("uri")
+LONG = (
+    read("README.rst") + "\n\n" +
+    "Release Information\n" +
+    "===================\n\n" +
+    re.search("(\d+.\d.\d \(.*?\)\n.*?)\n\n\n----\n\n\n",
+              read("CHANGELOG.rst"), re.S).group(1) +
+    "\n\n`Full changelog " +
+    "<{uri}en/stable/changelog.html>`_.\n\n".format(uri=URI) +
+    read("AUTHORS.rst")
+)
+
+
+if __name__ == "__main__":
+    setup(
+        name=NAME,
+        description=find_meta("description"),
+        license=find_meta("license"),
+        url=URI,
+        version=VERSION,
+        author=find_meta("author"),
+        author_email=find_meta("email"),
+        maintainer=find_meta("author"),
+        maintainer_email=find_meta("email"),
+        keywords=KEYWORDS,
+        long_description=LONG,
+        packages=PACKAGES,
+        package_dir={"": "src"},
+        zip_safe=False,
+        classifiers=CLASSIFIERS,
+        install_requires=INSTALL_REQUIRES,
+        extras_require=EXTRAS_REQUIRE,
+    )
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/__init__.py
@@ -0,0 +1,57 @@
+from __future__ import absolute_import, division, print_function
+
+from functools import partial
+
+from . import converters, exceptions, filters, validators
+from ._config import get_run_validators, set_run_validators
+from ._funcs import asdict, assoc, astuple, evolve, has
+from ._make import (
+    NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict,
+    make_class, validate
+)
+
+
+__version__ = "18.1.0"
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__uri__ = "http://www.attrs.org/"
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = partial(attrs, auto_attribs=True)  # happy Easter ;)
+
+__all__ = [
+    "Attribute",
+    "Factory",
+    "NOTHING",
+    "asdict",
+    "assoc",
+    "astuple",
+    "attr",
+    "attrib",
+    "attributes",
+    "attrs",
+    "converters",
+    "evolve",
+    "exceptions",
+    "fields",
+    "fields_dict",
+    "filters",
+    "get_run_validators",
+    "has",
+    "ib",
+    "make_class",
+    "s",
+    "set_run_validators",
+    "validate",
+    "validators",
+]
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/_compat.py
@@ -0,0 +1,146 @@
+from __future__ import absolute_import, division, print_function
+
+import platform
+import sys
+import types
+import warnings
+
+
+PY2 = sys.version_info[0] == 2
+PYPY = platform.python_implementation() == "PyPy"
+
+
+if PYPY or sys.version_info[:2] >= (3, 6):
+    ordered_dict = dict
+else:
+    from collections import OrderedDict
+    ordered_dict = OrderedDict
+
+
+if PY2:
+    from UserDict import IterableUserDict
+
+    # We 'bundle' isclass instead of using inspect as importing inspect is
+    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
+    def isclass(klass):
+        return isinstance(klass, (type, types.ClassType))
+
+    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
+    TYPE = "type"
+
+    def iteritems(d):
+        return d.iteritems()
+
+    # Python 2 is bereft of a read-only dict proxy, so we make one!
+    class ReadOnlyDict(IterableUserDict):
+        """
+        Best-effort read-only dict wrapper.
+        """
+
+        def __setitem__(self, key, val):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "assignment")
+
+        def update(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'update'")
+
+        def __delitem__(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "deletion")
+
+        def clear(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'clear'")
+
+        def pop(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'pop'")
+
+        def popitem(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'popitem'")
+
+        def setdefault(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'setdefault'")
+
+        def __repr__(self):
+            # Override to be identical to the Python 3 version.
+            return "mappingproxy(" + repr(self.data) + ")"
+
+    def metadata_proxy(d):
+        res = ReadOnlyDict()
+        res.data.update(d)  # We blocked update, so we have to do it like this.
+        return res
+
+else:
+    def isclass(klass):
+        return isinstance(klass, type)
+
+    TYPE = "class"
+
+    def iteritems(d):
+        return d.items()
+
+    def metadata_proxy(d):
+        return types.MappingProxyType(dict(d))
+
+
+def import_ctypes():
+    """
+    Moved into a function for testability.
+    """
+    import ctypes
+    return ctypes
+
+
+if not PY2:
+    def just_warn(*args, **kw):
+        """
+        We only warn on Python 3 because we are not aware of any concrete
+        consequences of not setting the cell on Python 2.
+        """
+        warnings.warn(
+            "Missing ctypes.  Some features like bare super() or accessing "
+            "__class__ will not work with slots classes.",
+            RuntimeWarning,
+            stacklevel=2,
+        )
+else:
+    def just_warn(*args, **kw):  # pragma: nocover
+        """
+        We only warn on Python 3 because we are not aware of any concrete
+        consequences of not setting the cell on Python 2.
+        """
+
+
+def make_set_closure_cell():
+    """
+    Moved into a function for testability.
+    """
+    if PYPY:  # pragma: no cover
+        def set_closure_cell(cell, value):
+            cell.__setstate__((value,))
+    else:
+        try:
+            ctypes = import_ctypes()
+
+            set_closure_cell = ctypes.pythonapi.PyCell_Set
+            set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
+            set_closure_cell.restype = ctypes.c_int
+        except Exception:
+            # We try best effort to set the cell, but sometimes it's not
+            # possible.  For example on Jython or on GAE.
+            set_closure_cell = just_warn
+    return set_closure_cell
+
+
+set_closure_cell = make_set_closure_cell()
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/_config.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+
+__all__ = ["set_run_validators", "get_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+    """
+    Set whether or not validators are run.  By default, they are run.
+    """
+    if not isinstance(run, bool):
+        raise TypeError("'run' must be bool.")
+    global _run_validators
+    _run_validators = run
+
+
+def get_run_validators():
+    """
+    Return whether or not validators are run.
+    """
+    return _run_validators
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/_funcs.py
@@ -0,0 +1,212 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, _obj_setattr, fields
+from .exceptions import AttrsAttributeNotFoundError
+
+
+def asdict(inst, recurse=True, filter=None, dict_factory=dict,
+           retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a dict.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable dict_factory: A callable to produce dictionaries from.  For
+        example, to produce ordered dictionaries instead of normal Python
+        dictionaries, pass in ``collections.OrderedDict``.
+    :param bool retain_collection_types: Do not convert to ``list`` when
+        encountering an attribute whose type is ``tuple`` or ``set``.  Only
+        meaningful if ``recurse`` is ``True``.
+
+    :rtype: return type of *dict_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.0.0 *dict_factory*
+    ..  versionadded:: 16.1.0 *retain_collection_types*
+    """
+    attrs = fields(inst.__class__)
+    rv = dict_factory()
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv[a.name] = asdict(v, recurse=True, filter=filter,
+                                    dict_factory=dict_factory)
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain_collection_types is True else list
+                rv[a.name] = cf([
+                    asdict(i, recurse=True, filter=filter,
+                           dict_factory=dict_factory)
+                    if has(i.__class__) else i
+                    for i in v
+                ])
+            elif isinstance(v, dict):
+                df = dict_factory
+                rv[a.name] = df((
+                    asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
+                    asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
+                    for kk, vv in iteritems(v))
+            else:
+                rv[a.name] = v
+        else:
+            rv[a.name] = v
+    return rv
+
+
+def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
+            retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a tuple.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable tuple_factory: A callable to produce tuples from.  For
+        example, to produce lists instead of tuples.
+    :param bool retain_collection_types: Do not convert to ``list``
+        or ``dict`` when encountering an attribute which type is
+        ``tuple``, ``dict`` or ``set``.  Only meaningful if ``recurse`` is
+        ``True``.
+
+    :rtype: return type of *tuple_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.2.0
+    """
+    attrs = fields(inst.__class__)
+    rv = []
+    retain = retain_collection_types  # Very long. :/
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv.append(astuple(v, recurse=True, filter=filter,
+                                  tuple_factory=tuple_factory,
+                                  retain_collection_types=retain))
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain is True else list
+                rv.append(cf([
+                    astuple(j, recurse=True, filter=filter,
+                            tuple_factory=tuple_factory,
+                            retain_collection_types=retain)
+                    if has(j.__class__) else j
+                    for j in v
+                ]))
+            elif isinstance(v, dict):
+                df = v.__class__ if retain is True else dict
+                rv.append(df(
+                        (
+                            astuple(
+                                kk,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(kk.__class__) else kk,
+                            astuple(
+                                vv,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(vv.__class__) else vv
+                        )
+                        for kk, vv in iteritems(v)))
+            else:
+                rv.append(v)
+        else:
+            rv.append(v)
+    return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+    """
+    Check whether *cls* is a class with ``attrs`` attributes.
+
+    :param type cls: Class to introspect.
+    :raise TypeError: If *cls* is not a class.
+
+    :rtype: :class:`bool`
+    """
+    return getattr(cls, "__attrs_attrs__", None) is not None
+
+
+def assoc(inst, **changes):
+    """
+    Copy *inst* and apply *changes*.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
+        be found on *cls*.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  deprecated:: 17.1.0
+        Use :func:`evolve` instead.
+    """
+    import warnings
+    warnings.warn("assoc is deprecated and will be removed after 2018/01.",
+                  DeprecationWarning, stacklevel=2)
+    new = copy.copy(inst)
+    attrs = fields(inst.__class__)
+    for k, v in iteritems(changes):
+        a = getattr(attrs, k, NOTHING)
+        if a is NOTHING:
+            raise AttrsAttributeNotFoundError(
+                "{k} is not an attrs attribute on {cl}."
+                .format(k=k, cl=new.__class__)
+            )
+        _obj_setattr(new, k, v)
+    return new
+
+
+def evolve(inst, **changes):
+    """
+    Create a new instance, based on *inst* with *changes* applied.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise TypeError: If *attr_name* couldn't be found in the class
+        ``__init__``.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 17.1.0
+    """
+    cls = inst.__class__
+    attrs = fields(cls)
+    for a in attrs:
+        if not a.init:
+            continue
+        attr_name = a.name  # To deal with private attributes.
+        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+        if init_name not in changes:
+            changes[init_name] = getattr(inst, attr_name)
+    return cls(**changes)
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/_make.py
@@ -0,0 +1,1689 @@
+from __future__ import absolute_import, division, print_function
+
+import hashlib
+import linecache
+import sys
+import threading
+import warnings
+
+from operator import itemgetter
+
+from . import _config
+from ._compat import (
+    PY2, isclass, iteritems, metadata_proxy, ordered_dict, set_closure_cell
+)
+from .exceptions import (
+    DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError,
+    UnannotatedAttributeError
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_converter_pat = "__attr_converter_{}"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = "    {attr_name} = property(itemgetter({index}))"
+_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar")
+
+_empty_metadata_singleton = metadata_proxy({})
+
+
+class _Nothing(object):
+    """
+    Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+    All instances of `_Nothing` are equal.
+    """
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, _):
+        return self
+
+    def __eq__(self, other):
+        return other.__class__ == _Nothing
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __repr__(self):
+        return "NOTHING"
+
+    def __hash__(self):
+        return 0xc0ffee
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+def attrib(default=NOTHING, validator=None,
+           repr=True, cmp=True, hash=None, init=True,
+           convert=None, metadata=None, type=None, converter=None,
+           factory=None):
+    """
+    Create a new attribute on a class.
+
+    ..  warning::
+
+        Does *not* do anything unless the class is also decorated with
+        :func:`attr.s`!
+
+    :param default: A value that is used if an ``attrs``-generated ``__init__``
+        is used and no value is passed while instantiating or the attribute is
+        excluded using ``init=False``.
+
+        If the value is an instance of :class:`Factory`, its callable will be
+        used to construct a new value (useful for mutable data types like lists
+        or dicts).
+
+        If a default is not set (or set manually to ``attr.NOTHING``), a value
+        *must* be supplied when instantiating; otherwise a :exc:`TypeError`
+        will be raised.
+
+        The default can also be set using decorator notation as shown below.
+
+    :type default: Any value.
+
+    :param callable factory: Syntactic sugar for
+        ``default=attr.Factory(callable)``.
+
+    :param validator: :func:`callable` that is called by ``attrs``-generated
+        ``__init__`` methods after the instance has been initialized.  They
+        receive the initialized instance, the :class:`Attribute`, and the
+        passed value.
+
+        The return value is *not* inspected so the validator has to throw an
+        exception itself.
+
+        If a ``list`` is passed, its items are treated as validators and must
+        all pass.
+
+        Validators can be globally disabled and re-enabled using
+        :func:`get_run_validators`.
+
+        The validator can also be set using decorator notation as shown below.
+
+    :type validator: ``callable`` or a ``list`` of ``callable``\\ s.
+
+    :param bool repr: Include this attribute in the generated ``__repr__``
+        method.
+    :param bool cmp: Include this attribute in the generated comparison methods
+        (``__eq__`` et al).
+    :param hash: Include this attribute in the generated ``__hash__``
+        method.  If ``None`` (default), mirror *cmp*'s value.  This is the
+        correct behavior according the Python spec.  Setting this value to
+        anything else than ``None`` is *discouraged*.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Include this attribute in the generated ``__init__``
+        method.  It is possible to set this to ``False`` and set a default
+        value.  In that case this attributed is unconditionally initialized
+        with the specified default value or factory.
+    :param callable converter: :func:`callable` that is called by
+        ``attrs``-generated ``__init__`` methods to converter attribute's value
+        to the desired format.  It is given the passed-in value, and the
+        returned value will be used as the new value of the attribute.  The
+        value is converted before being passed to the validator, if any.
+    :param metadata: An arbitrary mapping, to be used by third-party
+        components.  See :ref:`extending_metadata`.
+    :param type: The type of the attribute.  In Python 3.6 or greater, the
+        preferred method to specify the type is using a variable annotation
+        (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_).
+        This argument is provided for backward compatibility.
+        Regardless of the approach used, the type will be stored on
+        ``Attribute.type``.
+
+    .. versionadded:: 15.2.0 *convert*
+    .. versionadded:: 16.3.0 *metadata*
+    .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+    .. versionchanged:: 17.1.0
+       *hash* is ``None`` and therefore mirrors *cmp* by default.
+    .. versionadded:: 17.3.0 *type*
+    .. deprecated:: 17.4.0 *convert*
+    .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
+       *convert* to achieve consistency with other noun-based arguments.
+    .. versionadded:: 18.1.0
+       ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
+    """
+    if hash is not None and hash is not True and hash is not False:
+        raise TypeError(
+            "Invalid value for hash.  Must be True, False, or None."
+        )
+
+    if convert is not None:
+        if converter is not None:
+            raise RuntimeError(
+                "Can't pass both `convert` and `converter`.  "
+                "Please use `converter` only."
+            )
+        warnings.warn(
+            "The `convert` argument is deprecated in favor of `converter`.  "
+            "It will be removed after 2019/01.",
+            DeprecationWarning, stacklevel=2
+        )
+        converter = convert
+
+    if factory is not None:
+        if default is not NOTHING:
+            raise ValueError(
+                "The `default` and `factory` arguments are mutually "
+                "exclusive."
+            )
+        if not callable(factory):
+            raise ValueError(
+                "The `factory` argument must be a callable."
+            )
+        default = Factory(factory)
+
+    if metadata is None:
+        metadata = {}
+
+    return _CountingAttr(
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=cmp,
+        hash=hash,
+        init=init,
+        converter=converter,
+        metadata=metadata,
+        type=type,
+    )
+
+
+def _make_attr_tuple_class(cls_name, attr_names):
+    """
+    Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+    The subclass is a bare tuple with properties for names.
+
+    class MyClassAttributes(tuple):
+        __slots__ = ()
+        x = property(itemgetter(0))
+    """
+    attr_class_name = "{}Attributes".format(cls_name)
+    attr_class_template = [
+        "class {}(tuple):".format(attr_class_name),
+        "    __slots__ = ()",
+    ]
+    if attr_names:
+        for i, attr_name in enumerate(attr_names):
+            attr_class_template.append(_tuple_property_pat.format(
+                index=i,
+                attr_name=attr_name,
+            ))
+    else:
+        attr_class_template.append("    pass")
+    globs = {"itemgetter": itemgetter}
+    eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+    return globs[attr_class_name]
+
+
+# Tuple class for extracted attributes from a class definition.
+# `super_attrs` is a subset of `attrs`.
+_Attributes = _make_attr_tuple_class("_Attributes", [
+    "attrs",            # all attributes to build dunder methods for
+    "super_attrs",      # attributes that have been inherited
+    "super_attrs_map",  # map inherited attributes to their originating classes
+])
+
+
+def _is_class_var(annot):
+    """
+    Check whether *annot* is a typing.ClassVar.
+
+    The string comparison hack is used to avoid evaluating all string
+    annotations which would put attrs-based classes at a performance
+    disadvantage compared to plain old classes.
+    """
+    return str(annot).startswith(_classvar_prefixes)
+
+
+def _get_annotations(cls):
+    """
+    Get annotations for *cls*.
+    """
+    anns = getattr(cls, "__annotations__", None)
+    if anns is None:
+        return {}
+
+    # Verify that the annotations aren't merely inherited.
+    for super_cls in cls.__mro__[1:]:
+        if anns is getattr(super_cls, "__annotations__", None):
+            return {}
+
+    return anns
+
+
+def _counter_getter(e):
+    """
+    Key function for sorting to avoid re-creating a lambda for every class.
+    """
+    return e[1].counter
+
+
+def _transform_attrs(cls, these, auto_attribs):
+    """
+    Transform all `_CountingAttr`s on a class into `Attribute`s.
+
+    If *these* is passed, use that and don't look for them on the class.
+
+    Return an `_Attributes`.
+    """
+    cd = cls.__dict__
+    anns = _get_annotations(cls)
+
+    if these is not None:
+        ca_list = [
+            (name, ca)
+            for name, ca
+            in iteritems(these)
+        ]
+
+        if not isinstance(these, ordered_dict):
+            ca_list.sort(key=_counter_getter)
+    elif auto_attribs is True:
+        ca_names = {
+            name
+            for name, attr
+            in cd.items()
+            if isinstance(attr, _CountingAttr)
+        }
+        ca_list = []
+        annot_names = set()
+        for attr_name, type in anns.items():
+            if _is_class_var(type):
+                continue
+            annot_names.add(attr_name)
+            a = cd.get(attr_name, NOTHING)
+            if not isinstance(a, _CountingAttr):
+                if a is NOTHING:
+                    a = attrib()
+                else:
+                    a = attrib(default=a)
+            ca_list.append((attr_name, a))
+
+        unannotated = ca_names - annot_names
+        if len(unannotated) > 0:
+            raise UnannotatedAttributeError(
+                "The following `attr.ib`s lack a type annotation: " +
+                ", ".join(sorted(
+                    unannotated,
+                    key=lambda n: cd.get(n).counter
+                )) + "."
+            )
+    else:
+        ca_list = sorted((
+            (name, attr)
+            for name, attr
+            in cd.items()
+            if isinstance(attr, _CountingAttr)
+        ), key=lambda e: e[1].counter)
+
+    own_attrs = [
+        Attribute.from_counting_attr(
+            name=attr_name,
+            ca=ca,
+            type=anns.get(attr_name),
+        )
+        for attr_name, ca
+        in ca_list
+    ]
+
+    super_attrs = []
+    super_attr_map = {}  # A dictionary of superattrs to their classes.
+    taken_attr_names = {a.name: a for a in own_attrs}
+
+    # Traverse the MRO and collect attributes.
+    for super_cls in cls.__mro__[1:-1]:
+        sub_attrs = getattr(super_cls, "__attrs_attrs__", None)
+        if sub_attrs is not None:
+            for a in sub_attrs:
+                prev_a = taken_attr_names.get(a.name)
+                # Only add an attribute if it hasn't been defined before.  This
+                # allows for overwriting attribute definitions by subclassing.
+                if prev_a is None:
+                    super_attrs.append(a)
+                    taken_attr_names[a.name] = a
+                    super_attr_map[a.name] = super_cls
+
+    attr_names = [a.name for a in super_attrs + own_attrs]
+
+    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+    attrs = AttrsClass(
+        super_attrs + [
+            Attribute.from_counting_attr(
+                name=attr_name,
+                ca=ca,
+                type=anns.get(attr_name)
+            )
+            for attr_name, ca
+            in ca_list
+        ]
+    )
+
+    had_default = False
+    for a in attrs:
+        if had_default is True and a.default is NOTHING and a.init is True:
+            raise ValueError(
+                "No mandatory attributes allowed after an attribute with a "
+                "default value or factory.  Attribute in question: {a!r}"
+                .format(a=a)
+            )
+        elif had_default is False and \
+                a.default is not NOTHING and \
+                a.init is not False:
+            had_default = True
+
+    return _Attributes((attrs, super_attrs, super_attr_map))
+
+
+def _frozen_setattrs(self, name, value):
+    """
+    Attached to frozen classes as __setattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def _frozen_delattrs(self, name):
+    """
+    Attached to frozen classes as __delattr__.
+    """
+    raise FrozenInstanceError()
+
+
+class _ClassBuilder(object):
+    """
+    Iteratively build *one* class.
+    """
+    __slots__ = (
+        "_cls", "_cls_dict", "_attrs", "_super_names", "_attr_names", "_slots",
+        "_frozen", "_has_post_init", "_delete_attribs", "_super_attr_map",
+    )
+
+    def __init__(self, cls, these, slots, frozen, auto_attribs):
+        attrs, super_attrs, super_map = _transform_attrs(
+            cls, these, auto_attribs
+        )
+
+        self._cls = cls
+        self._cls_dict = dict(cls.__dict__) if slots else {}
+        self._attrs = attrs
+        self._super_names = set(a.name for a in super_attrs)
+        self._super_attr_map = super_map
+        self._attr_names = tuple(a.name for a in attrs)
+        self._slots = slots
+        self._frozen = frozen or _has_frozen_superclass(cls)
+        self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
+        self._delete_attribs = not bool(these)
+
+        self._cls_dict["__attrs_attrs__"] = self._attrs
+
+        if frozen:
+            self._cls_dict["__setattr__"] = _frozen_setattrs
+            self._cls_dict["__delattr__"] = _frozen_delattrs
+
+    def __repr__(self):
+        return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__)
+
+    def build_class(self):
+        """
+        Finalize class based on the accumulated configuration.
+
+        Builder cannot be used anymore after calling this method.
+        """
+        if self._slots is True:
+            return self._create_slots_class()
+        else:
+            return self._patch_original_class()
+
+    def _patch_original_class(self):
+        """
+        Apply accumulated methods and return the class.
+        """
+        cls = self._cls
+        super_names = self._super_names
+
+        # Clean class of attribute definitions (`attr.ib()`s).
+        if self._delete_attribs:
+            for name in self._attr_names:
+                if name not in super_names and \
+                        getattr(cls, name, None) is not None:
+                    delattr(cls, name)
+
+        # Attach our dunder methods.
+        for name, value in self._cls_dict.items():
+            setattr(cls, name, value)
+
+        return cls
+
+    def _create_slots_class(self):
+        """
+        Build and return a new class with a `__slots__` attribute.
+        """
+        super_names = self._super_names
+        cd = {
+            k: v
+            for k, v in iteritems(self._cls_dict)
+            if k not in tuple(self._attr_names) + ("__dict__",)
+        }
+
+        # We only add the names of attributes that aren't inherited.
+        # Settings __slots__ to inherited attributes wastes memory.
+        cd["__slots__"] = tuple(
+            name
+            for name in self._attr_names
+            if name not in super_names
+        )
+
+        qualname = getattr(self._cls, "__qualname__", None)
+        if qualname is not None:
+            cd["__qualname__"] = qualname
+
+        # __weakref__ is not writable.
+        state_attr_names = tuple(
+            an for an in self._attr_names if an != "__weakref__"
+        )
+
+        def slots_getstate(self):
+            """
+            Automatically created by attrs.
+            """
+            return tuple(
+                getattr(self, name)
+                for name in state_attr_names
+            )
+
+        def slots_setstate(self, state):
+            """
+            Automatically created by attrs.
+            """
+            __bound_setattr = _obj_setattr.__get__(self, Attribute)
+            for name, value in zip(state_attr_names, state):
+                __bound_setattr(name, value)
+
+        # slots and frozen require __getstate__/__setstate__ to work
+        cd["__getstate__"] = slots_getstate
+        cd["__setstate__"] = slots_setstate
+
+        # Create new class based on old class and our methods.
+        cls = type(self._cls)(
+            self._cls.__name__,
+            self._cls.__bases__,
+            cd,
+        )
+
+        # The following is a fix for
+        # https://github.com/python-attrs/attrs/issues/102.  On Python 3,
+        # if a method mentions `__class__` or uses the no-arg super(), the
+        # compiler will bake a reference to the class in the method itself
+        # as `method.__closure__`.  Since we replace the class with a
+        # clone, we rewrite these references so it keeps working.
+        for item in cls.__dict__.values():
+            if isinstance(item, (classmethod, staticmethod)):
+                # Class- and staticmethods hide their functions inside.
+                # These might need to be rewritten as well.
+                closure_cells = getattr(item.__func__, "__closure__", None)
+            else:
+                closure_cells = getattr(item, "__closure__", None)
+
+            if not closure_cells:  # Catch None or the empty list.
+                continue
+            for cell in closure_cells:
+                if cell.cell_contents is self._cls:
+                    set_closure_cell(cell, cls)
+
+        return cls
+
+    def add_repr(self, ns):
+        self._cls_dict["__repr__"] = self._add_method_dunders(
+            _make_repr(self._attrs, ns=ns)
+        )
+        return self
+
+    def add_str(self):
+        repr = self._cls_dict.get("__repr__")
+        if repr is None:
+            raise ValueError(
+                "__str__ can only be generated if a __repr__ exists."
+            )
+
+        def __str__(self):
+            return self.__repr__()
+
+        self._cls_dict["__str__"] = self._add_method_dunders(__str__)
+        return self
+
+    def make_unhashable(self):
+        self._cls_dict["__hash__"] = None
+        return self
+
+    def add_hash(self):
+        self._cls_dict["__hash__"] = self._add_method_dunders(
+            _make_hash(self._attrs)
+        )
+
+        return self
+
+    def add_init(self):
+        self._cls_dict["__init__"] = self._add_method_dunders(
+            _make_init(
+                self._attrs,
+                self._has_post_init,
+                self._frozen,
+                self._slots,
+                self._super_attr_map,
+            )
+        )
+
+        return self
+
+    def add_cmp(self):
+        cd = self._cls_dict
+
+        cd["__eq__"], cd["__ne__"], cd["__lt__"], cd["__le__"], cd["__gt__"], \
+            cd["__ge__"] = (
+                self._add_method_dunders(meth)
+                for meth in _make_cmp(self._attrs)
+            )
+
+        return self
+
+    def _add_method_dunders(self, method):
+        """
+        Add __module__ and __qualname__ to a *method* if possible.
+        """
+        try:
+            method.__module__ = self._cls.__module__
+        except AttributeError:
+            pass
+
+        try:
+            method.__qualname__ = ".".join(
+                (self._cls.__qualname__, method.__name__,)
+            )
+        except AttributeError:
+            pass
+
+        return method
+
+
+def attrs(maybe_cls=None, these=None, repr_ns=None,
+          repr=True, cmp=True, hash=None, init=True,
+          slots=False, frozen=False, str=False, auto_attribs=False):
+    r"""
+    A class decorator that adds `dunder
+    <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
+    specified attributes using :func:`attr.ib` or the *these* argument.
+
+    :param these: A dictionary of name to :func:`attr.ib` mappings.  This is
+        useful to avoid the definition of your attributes within the class body
+        because you can't (e.g. if you want to add ``__repr__`` methods to
+        Django models) or don't want to.
+
+        If *these* is not ``None``, ``attrs`` will *not* search the class body
+        for attributes and will *not* remove any attributes from it.
+
+        If *these* is an ordered dict (:class:`dict` on Python 3.6+,
+        :class:`collections.OrderedDict` otherwise), the order is deduced from
+        the order of the attributes inside *these*.  Otherwise the order
+        of the definition of the attributes is used.
+
+    :type these: :class:`dict` of :class:`str` to :func:`attr.ib`
+
+    :param str repr_ns: When using nested classes, there's no way in Python 2
+        to automatically detect that.  Therefore it's possible to set the
+        namespace explicitly for a more meaningful ``repr`` output.
+    :param bool repr: Create a ``__repr__`` method with a human readable
+        representation of ``attrs`` attributes..
+    :param bool str: Create a ``__str__`` method that is identical to
+        ``__repr__``.  This is usually not necessary except for
+        :class:`Exception`\ s.
+    :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``,
+        ``__gt__``, and ``__ge__`` methods that compare the class as if it were
+        a tuple of its ``attrs`` attributes.  But the attributes are *only*
+        compared, if the type of both classes is *identical*!
+    :param hash: If ``None`` (default), the ``__hash__`` method is generated
+        according how *cmp* and *frozen* are set.
+
+        1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you.
+        2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to
+           None, marking it unhashable (which it is).
+        3. If *cmp* is False, ``__hash__`` will be left untouched meaning the
+           ``__hash__`` method of the superclass will be used (if superclass is
+           ``object``, this means it will fall back to id-based hashing.).
+
+        Although not recommended, you can decide for yourself and force
+        ``attrs`` to create one (e.g. if the class is immutable even though you
+        didn't freeze it programmatically) by passing ``True`` or not.  Both of
+        these cases are rather special and should be used carefully.
+
+        See the `Python documentation \
+        <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_
+        and the `GitHub issue that led to the default behavior \
+        <https://github.com/python-attrs/attrs/issues/136>`_ for more details.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Create a ``__init__`` method that initializes the
+        ``attrs`` attributes.  Leading underscores are stripped for the
+        argument name.  If a ``__attrs_post_init__`` method exists on the
+        class, it will be called after the class is fully initialized.
+    :param bool slots: Create a slots_-style class that's more
+        memory-efficient.  See :ref:`slots` for further ramifications.
+    :param bool frozen: Make instances immutable after initialization.  If
+        someone attempts to modify a frozen instance,
+        :exc:`attr.exceptions.FrozenInstanceError` is raised.
+
+        Please note:
+
+            1. This is achieved by installing a custom ``__setattr__`` method
+               on your class so you can't implement an own one.
+
+            2. True immutability is impossible in Python.
+
+            3. This *does* have a minor a runtime performance :ref:`impact
+               <how-frozen>` when initializing new instances.  In other words:
+               ``__init__`` is slightly slower with ``frozen=True``.
+
+            4. If a class is frozen, you cannot modify ``self`` in
+               ``__attrs_post_init__`` or a self-written ``__init__``. You can
+               circumvent that limitation by using
+               ``object.__setattr__(self, "attribute_name", value)``.
+
+        ..  _slots: https://docs.python.org/3/reference/datamodel.html#slots
+    :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes
+        (Python 3.6 and later only) from the class body.
+
+        In this case, you **must** annotate every field.  If ``attrs``
+        encounters a field that is set to an :func:`attr.ib` but lacks a type
+        annotation, an :exc:`attr.exceptions.UnannotatedAttributeError` is
+        raised.  Use ``field_name: typing.Any = attr.ib(...)`` if you don't
+        want to set a type.
+
+        If you assign a value to those attributes (e.g. ``x: int = 42``), that
+        value becomes the default value like if it were passed using
+        ``attr.ib(default=42)``.  Passing an instance of :class:`Factory` also
+        works as expected.
+
+        Attributes annotated as :data:`typing.ClassVar` are **ignored**.
+
+        .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/
+
+    .. versionadded:: 16.0.0 *slots*
+    .. versionadded:: 16.1.0 *frozen*
+    .. versionadded:: 16.3.0 *str*
+    .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
+    .. versionchanged:: 17.1.0
+       *hash* supports ``None`` as value which is also the default now.
+    .. versionadded:: 17.3.0 *auto_attribs*
+    .. versionchanged:: 18.1.0
+       If *these* is passed, no attributes are deleted from the class body.
+    .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
+    """
+    def wrap(cls):
+        if getattr(cls, "__class__", None) is None:
+            raise TypeError("attrs only works with new-style classes.")
+
+        builder = _ClassBuilder(cls, these, slots, frozen, auto_attribs)
+
+        if repr is True:
+            builder.add_repr(repr_ns)
+        if str is True:
+            builder.add_str()
+        if cmp is True:
+            builder.add_cmp()
+
+        if hash is not True and hash is not False and hash is not None:
+            # Can't use `hash in` because 1 == True for example.
+            raise TypeError(
+                "Invalid value for hash.  Must be True, False, or None."
+            )
+        elif hash is False or (hash is None and cmp is False):
+            pass
+        elif hash is True or (hash is None and cmp is True and frozen is True):
+            builder.add_hash()
+        else:
+            builder.make_unhashable()
+
+        if init is True:
+            builder.add_init()
+
+        return builder.build_class()
+
+    # maybe_cls's type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attrs` but ``None`` if used as `@attrs()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+_attrs = attrs
+"""
+Internal alias so we can use it in functions that take an argument called
+*attrs*.
+"""
+
+
+if PY2:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return (
+            getattr(
+                cls.__setattr__, "__module__", None
+            ) == _frozen_setattrs.__module__ and
+            cls.__setattr__.__name__ == _frozen_setattrs.__name__
+        )
+else:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return cls.__setattr__ == _frozen_setattrs
+
+
+def _attrs_to_tuple(obj, attrs):
+    """
+    Create a tuple of all values of *obj*'s *attrs*.
+    """
+    return tuple(getattr(obj, a.name) for a in attrs)
+
+
+def _make_hash(attrs):
+    attrs = tuple(
+        a
+        for a in attrs
+        if a.hash is True or (a.hash is None and a.cmp is True)
+    )
+
+    # We cache the generated hash methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated hash %s>" % (sha1.hexdigest(),)
+    type_hash = hash(unique_filename)
+    lines = [
+        "def __hash__(self):",
+        "    return hash((",
+        "        %d," % (type_hash,),
+    ]
+    for a in attrs:
+        lines.append("        self.%s," % (a.name))
+
+    lines.append("    ))")
+
+    script = "\n".join(lines)
+    globs = {}
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+
+    return locs["__hash__"]
+
+
+def _add_hash(cls, attrs):
+    """
+    Add a hash method to *cls*.
+    """
+    cls.__hash__ = _make_hash(attrs)
+    return cls
+
+
+def __ne__(self, other):
+    """
+    Check equality and either forward a NotImplemented or return the result
+    negated.
+    """
+    result = self.__eq__(other)
+    if result is NotImplemented:
+        return NotImplemented
+
+    return not result
+
+
+def _make_cmp(attrs):
+    attrs = [a for a in attrs if a.cmp]
+
+    # We cache the generated eq methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated eq %s>" % (sha1.hexdigest(),)
+    lines = [
+        "def __eq__(self, other):",
+        "    if other.__class__ is not self.__class__:",
+        "        return NotImplemented",
+    ]
+    # We can't just do a big self.x = other.x and... clause due to
+    # irregularities like nan == nan is false but (nan,) == (nan,) is true.
+    if attrs:
+        lines.append("    return  (")
+        others = [
+            "    ) == (",
+        ]
+        for a in attrs:
+            lines.append("        self.%s," % (a.name,))
+            others.append("        other.%s," % (a.name,))
+
+        lines += others + ["    )"]
+    else:
+        lines.append("    return True")
+
+    script = "\n".join(lines)
+    globs = {}
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+    eq = locs["__eq__"]
+    ne = __ne__
+
+    def attrs_to_tuple(obj):
+        """
+        Save us some typing.
+        """
+        return _attrs_to_tuple(obj, attrs)
+
+    def __lt__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) < attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def __le__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) <= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def __gt__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) > attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def __ge__(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) >= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    return eq, ne, __lt__, __le__, __gt__, __ge__
+
+
+def _add_cmp(cls, attrs=None):
+    """
+    Add comparison methods to *cls*.
+    """
+    if attrs is None:
+        attrs = cls.__attrs_attrs__
+
+    cls.__eq__, cls.__ne__, cls.__lt__, cls.__le__, cls.__gt__, cls.__ge__ = \
+        _make_cmp(attrs)
+
+    return cls
+
+
+_already_repring = threading.local()
+
+
+def _make_repr(attrs, ns):
+    """
+    Make a repr method for *attr_names* adding *ns* to the full name.
+    """
+    attr_names = tuple(
+        a.name
+        for a in attrs
+        if a.repr
+    )
+
+    def __repr__(self):
+        """
+        Automatically created by attrs.
+        """
+        try:
+            working_set = _already_repring.working_set
+        except AttributeError:
+            working_set = set()
+            _already_repring.working_set = working_set
+
+        if id(self) in working_set:
+            return "..."
+        real_cls = self.__class__
+        if ns is None:
+            qualname = getattr(real_cls, "__qualname__", None)
+            if qualname is not None:
+                class_name = qualname.rsplit(">.", 1)[-1]
+            else:
+                class_name = real_cls.__name__
+        else:
+            class_name = ns + "." + real_cls.__name__
+
+        # Since 'self' remains on the stack (i.e.: strongly referenced) for the
+        # duration of this call, it's safe to depend on id(...) stability, and
+        # not need to track the instance and therefore worry about properties
+        # like weakref- or hash-ability.
+        working_set.add(id(self))
+        try:
+            result = [class_name, "("]
+            first = True
+            for name in attr_names:
+                if first:
+                    first = False
+                else:
+                    result.append(", ")
+                result.extend((name, "=", repr(getattr(self, name, NOTHING))))
+            return "".join(result) + ")"
+        finally:
+            working_set.remove(id(self))
+    return __repr__
+
+
+def _add_repr(cls, ns=None, attrs=None):
+    """
+    Add a repr method to *cls*.
+    """
+    if attrs is None:
+        attrs = cls.__attrs_attrs__
+
+    cls.__repr__ = _make_repr(attrs, ns)
+    return cls
+
+
+def _make_init(attrs, post_init, frozen, slots, super_attr_map):
+    attrs = [
+        a
+        for a in attrs
+        if a.init or a.default is not NOTHING
+    ]
+
+    # We cache the generated init methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated init {0}>".format(
+        sha1.hexdigest()
+    )
+
+    script, globs, annotations = _attrs_to_init_script(
+        attrs,
+        frozen,
+        slots,
+        post_init,
+        super_attr_map,
+    )
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    attr_dict = dict((a.name, a) for a in attrs)
+    globs.update({
+        "NOTHING": NOTHING,
+        "attr_dict": attr_dict,
+    })
+    if frozen is True:
+        # Save the lookup overhead in __init__ if we need to circumvent
+        # immutability.
+        globs["_cached_setattr"] = _obj_setattr
+    eval(bytecode, globs, locs)
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename,
+    )
+
+    __init__ = locs["__init__"]
+    __init__.__annotations__ = annotations
+    return __init__
+
+
+def _add_init(cls, frozen):
+    """
+    Add a __init__ method to *cls*.  If *frozen* is True, make it immutable.
+    """
+    cls.__init__ = _make_init(
+        cls.__attrs_attrs__,
+        getattr(cls, "__attrs_post_init__", False),
+        frozen,
+        _is_slot_cls(cls),
+        {},
+    )
+    return cls
+
+
+def fields(cls):
+    """
+    Return the tuple of ``attrs`` attributes for a class.
+
+    The tuple also allows accessing the fields by their names (see below for
+    examples).
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: tuple (with name accessors) of :class:`attr.Attribute`
+
+    ..  versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+        by name.
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return attrs
+
+
+def fields_dict(cls):
+    """
+    Return an ordered dictionary of ``attrs`` attributes for a class, whose
+    keys are the attribute names.
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: an ordered dict where keys are attribute names and values are
+        :class:`attr.Attribute`\\ s. This will be a :class:`dict` if it's
+        naturally ordered like on Python 3.6+ or an
+        :class:`~collections.OrderedDict` otherwise.
+
+    .. versionadded:: 18.1.0
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return ordered_dict(((a.name, a) for a in attrs))
+
+
+def validate(inst):
+    """
+    Validate all attributes on *inst* that have a validator.
+
+    Leaves all exceptions through.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    """
+    if _config._run_validators is False:
+        return
+
+    for a in fields(inst.__class__):
+        v = a.validator
+        if v is not None:
+            v(inst, a, getattr(inst, a.name))
+
+
+def _is_slot_cls(cls):
+    return "__slots__" in cls.__dict__
+
+
+def _is_slot_attr(a_name, super_attr_map):
+    """
+    Check if the attribute name comes from a slot class.
+    """
+    return a_name in super_attr_map and _is_slot_cls(super_attr_map[a_name])
+
+
+def _attrs_to_init_script(attrs, frozen, slots, post_init, super_attr_map):
+    """
+    Return a script of an initializer for *attrs* and a dict of globals.
+
+    The globals are expected by the generated script.
+
+    If *frozen* is True, we cannot set the attributes directly so we use
+    a cached ``object.__setattr__``.
+    """
+    lines = []
+    any_slot_ancestors = any(
+        _is_slot_attr(a.name, super_attr_map)
+        for a in attrs
+    )
+    if frozen is True:
+        if slots is True:
+            lines.append(
+                # Circumvent the __setattr__ descriptor to save one lookup per
+                # assignment.
+                "_setattr = _cached_setattr.__get__(self, self.__class__)"
+            )
+
+            def fmt_setter(attr_name, value_var):
+                return "_setattr('%(attr_name)s', %(value_var)s)" % {
+                    "attr_name": attr_name,
+                    "value_var": value_var,
+                }
+
+            def fmt_setter_with_converter(attr_name, value_var):
+                conv_name = _init_converter_pat.format(attr_name)
+                return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
+                    "attr_name": attr_name,
+                    "value_var": value_var,
+                    "conv": conv_name,
+                }
+        else:
+            # Dict frozen classes assign directly to __dict__.
+            # But only if the attribute doesn't come from an ancestor slot
+            # class.
+            lines.append(
+                "_inst_dict = self.__dict__"
+            )
+            if any_slot_ancestors:
+                lines.append(
+                    # Circumvent the __setattr__ descriptor to save one lookup
+                    # per assignment.
+                    "_setattr = _cached_setattr.__get__(self, self.__class__)"
+                )
+
+            def fmt_setter(attr_name, value_var):
+                if _is_slot_attr(attr_name, super_attr_map):
+                    res = "_setattr('%(attr_name)s', %(value_var)s)" % {
+                        "attr_name": attr_name,
+                        "value_var": value_var,
+                    }
+                else:
+                    res = "_inst_dict['%(attr_name)s'] = %(value_var)s" % {
+                        "attr_name": attr_name,
+                        "value_var": value_var,
+                    }
+                return res
+
+            def fmt_setter_with_converter(attr_name, value_var):
+                conv_name = _init_converter_pat.format(attr_name)
+                if _is_slot_attr(attr_name, super_attr_map):
+                    tmpl = "_setattr('%(attr_name)s', %(c)s(%(value_var)s))"
+                else:
+                    tmpl = "_inst_dict['%(attr_name)s'] = %(c)s(%(value_var)s)"
+                return tmpl % {
+                    "attr_name": attr_name,
+                    "value_var": value_var,
+                    "c": conv_name,
+                }
+    else:
+        # Not frozen.
+        def fmt_setter(attr_name, value):
+            return "self.%(attr_name)s = %(value)s" % {
+                "attr_name": attr_name,
+                "value": value,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_converter_pat.format(attr_name)
+            return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+
+    args = []
+    attrs_to_validate = []
+
+    # This is a dictionary of names to validator and converter callables.
+    # Injecting this into __init__ globals lets us avoid lookups.
+    names_for_globals = {}
+    annotations = {'return': None}
+
+    for a in attrs:
+        if a.validator:
+            attrs_to_validate.append(a)
+        attr_name = a.name
+        arg_name = a.name.lstrip("_")
+        has_factory = isinstance(a.default, Factory)
+        if has_factory and a.default.takes_self:
+            maybe_self = "self"
+        else:
+            maybe_self = ""
+        if a.init is False:
+            if has_factory:
+                init_factory_name = _init_factory_pat.format(a.name)
+                if a.converter is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)))
+                    conv_name = _init_converter_pat.format(a.name)
+                    names_for_globals[conv_name] = a.converter
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)
+                    ))
+                names_for_globals[init_factory_name] = a.default.factory
+            else:
+                if a.converter is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+                    conv_name = _init_converter_pat.format(a.name)
+                    names_for_globals[conv_name] = a.converter
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+        elif a.default is not NOTHING and not has_factory:
+            args.append(
+                "{arg_name}=attr_dict['{attr_name}'].default".format(
+                    arg_name=arg_name,
+                    attr_name=attr_name,
+                )
+            )
+            if a.converter is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_converter_pat.format(a.name)] = (
+                    a.converter
+                )
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+        elif has_factory:
+            args.append("{arg_name}=NOTHING".format(arg_name=arg_name))
+            lines.append("if {arg_name} is not NOTHING:"
+                         .format(arg_name=arg_name))
+            init_factory_name = _init_factory_pat.format(a.name)
+            if a.converter is not None:
+                lines.append("    " + fmt_setter_with_converter(
+                    attr_name, arg_name
+                ))
+                lines.append("else:")
+                lines.append("    " + fmt_setter_with_converter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+                names_for_globals[_init_converter_pat.format(a.name)] = (
+                    a.converter
+                )
+            else:
+                lines.append("    " + fmt_setter(attr_name, arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+            names_for_globals[init_factory_name] = a.default.factory
+        else:
+            args.append(arg_name)
+            if a.converter is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_converter_pat.format(a.name)] = (
+                    a.converter
+                )
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+
+        if a.init is True and a.converter is None and a.type is not None:
+            annotations[arg_name] = a.type
+
+    if attrs_to_validate:  # we can skip this if there are no validators.
+        names_for_globals["_config"] = _config
+        lines.append("if _config._run_validators is True:")
+        for a in attrs_to_validate:
+            val_name = "__attr_validator_{}".format(a.name)
+            attr_name = "__attr_{}".format(a.name)
+            lines.append("    {}(self, {}, self.{})".format(
+                val_name, attr_name, a.name))
+            names_for_globals[val_name] = a.validator
+            names_for_globals[attr_name] = a
+    if post_init:
+        lines.append("self.__attrs_post_init__()")
+
+    return """\
+def __init__(self, {args}):
+    {lines}
+""".format(
+        args=", ".join(args),
+        lines="\n    ".join(lines) if lines else "pass",
+    ), names_for_globals, annotations
+
+
+class Attribute(object):
+    """
+    *Read-only* representation of an attribute.
+
+    :attribute name: The name of the attribute.
+
+    Plus *all* arguments of :func:`attr.ib`.
+
+    For the version history of the fields, see :func:`attr.ib`.
+    """
+    __slots__ = (
+        "name", "default", "validator", "repr", "cmp", "hash", "init",
+        "metadata", "type", "converter",
+    )
+
+    def __init__(self, name, default, validator, repr, cmp, hash, init,
+                 convert=None, metadata=None, type=None, converter=None):
+        # Cache this descriptor here to speed things up later.
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+
+        # Despite the big red warning, people *do* instantiate `Attribute`
+        # themselves.
+        if convert is not None:
+            if converter is not None:
+                raise RuntimeError(
+                    "Can't pass both `convert` and `converter`.  "
+                    "Please use `converter` only."
+                )
+            warnings.warn(
+                "The `convert` argument is deprecated in favor of `converter`."
+                "  It will be removed after 2019/01.",
+                DeprecationWarning, stacklevel=2
+            )
+            converter = convert
+
+        bound_setattr("name", name)
+        bound_setattr("default", default)
+        bound_setattr("validator", validator)
+        bound_setattr("repr", repr)
+        bound_setattr("cmp", cmp)
+        bound_setattr("hash", hash)
+        bound_setattr("init", init)
+        bound_setattr("converter", converter)
+        bound_setattr("metadata", (
+            metadata_proxy(metadata) if metadata
+            else _empty_metadata_singleton
+        ))
+        bound_setattr("type", type)
+
+    def __setattr__(self, name, value):
+        raise FrozenInstanceError()
+
+    @property
+    def convert(self):
+        warnings.warn(
+            "The `convert` attribute is deprecated in favor of `converter`.  "
+            "It will be removed after 2019/01.",
+            DeprecationWarning, stacklevel=2,
+        )
+        return self.converter
+
+    @classmethod
+    def from_counting_attr(cls, name, ca, type=None):
+        # type holds the annotated value. deal with conflicts:
+        if type is None:
+            type = ca.type
+        elif ca.type is not None:
+            raise ValueError(
+                "Type annotation and type argument cannot both be present"
+            )
+        inst_dict = {
+            k: getattr(ca, k)
+            for k
+            in Attribute.__slots__
+            if k not in (
+                "name", "validator", "default", "type", "convert",
+            )  # exclude methods and deprecated alias
+        }
+        return cls(
+            name=name, validator=ca._validator, default=ca._default, type=type,
+            **inst_dict
+        )
+
+    # Don't use _add_pickle since fields(Attribute) doesn't work
+    def __getstate__(self):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(self, name) if name != "metadata"
+                     else dict(self.metadata)
+                     for name in self.__slots__)
+
+    def __setstate__(self, state):
+        """
+        Play nice with pickle.
+        """
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+        for name, value in zip(self.__slots__, state):
+            if name != "metadata":
+                bound_setattr(name, value)
+            else:
+                bound_setattr(name, metadata_proxy(value) if value else
+                              _empty_metadata_singleton)
+
+
+_a = [
+    Attribute(name=name, default=NOTHING, validator=None,
+              repr=True, cmp=True, hash=(name != "metadata"), init=True)
+    for name in Attribute.__slots__
+    if name != "convert"  # XXX: remove once `convert` is gone
+]
+
+Attribute = _add_hash(
+    _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a),
+    attrs=[a for a in _a if a.hash]
+)
+
+
+class _CountingAttr(object):
+    """
+    Intermediate representation of attributes that uses a counter to preserve
+    the order in which the attributes have been defined.
+
+    *Internal* data structure of the attrs library.  Running into is most
+    likely the result of a bug like a forgotten `@attr.s` decorator.
+    """
+    __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init",
+                 "metadata", "_validator", "converter", "type")
+    __attrs_attrs__ = tuple(
+        Attribute(name=name, default=NOTHING, validator=None,
+                  repr=True, cmp=True, hash=True, init=True)
+        for name
+        in ("counter", "_default", "repr", "cmp", "hash", "init",)
+    ) + (
+        Attribute(name="metadata", default=None, validator=None,
+                  repr=True, cmp=True, hash=False, init=True),
+    )
+    cls_counter = 0
+
+    def __init__(self, default, validator, repr, cmp, hash, init, converter,
+                 metadata, type):
+        _CountingAttr.cls_counter += 1
+        self.counter = _CountingAttr.cls_counter
+        self._default = default
+        # If validator is a list/tuple, wrap it using helper validator.
+        if validator and isinstance(validator, (list, tuple)):
+            self._validator = and_(*validator)
+        else:
+            self._validator = validator
+        self.repr = repr
+        self.cmp = cmp
+        self.hash = hash
+        self.init = init
+        self.converter = converter
+        self.metadata = metadata
+        self.type = type
+
+    def validator(self, meth):
+        """
+        Decorator that adds *meth* to the list of validators.
+
+        Returns *meth* unchanged.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._validator is None:
+            self._validator = meth
+        else:
+            self._validator = and_(self._validator, meth)
+        return meth
+
+    def default(self, meth):
+        """
+        Decorator that allows to set the default for an attribute.
+
+        Returns *meth* unchanged.
+
+        :raises DefaultAlreadySetError: If default has been set before.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._default is not NOTHING:
+            raise DefaultAlreadySetError()
+
+        self._default = Factory(meth, takes_self=True)
+
+        return meth
+
+
+_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
+
+
+@attrs(slots=True, init=False, hash=True)
+class Factory(object):
+    """
+    Stores a factory callable.
+
+    If passed as the default value to :func:`attr.ib`, the factory is used to
+    generate a new value.
+
+    :param callable factory: A callable that takes either none or exactly one
+        mandatory positional argument depending on *takes_self*.
+    :param bool takes_self: Pass the partially initialized instance that is
+        being initialized as a positional argument.
+
+    .. versionadded:: 17.1.0  *takes_self*
+    """
+    factory = attrib()
+    takes_self = attrib()
+
+    def __init__(self, factory, takes_self=False):
+        """
+        `Factory` is part of the default machinery so if we want a default
+        value here, we have to implement it ourselves.
+        """
+        self.factory = factory
+        self.takes_self = takes_self
+
+
+def make_class(name, attrs, bases=(object,), **attributes_arguments):
+    """
+    A quick way to create a new class called *name* with *attrs*.
+
+    :param name: The name for the new class.
+    :type name: str
+
+    :param attrs: A list of names or a dictionary of mappings of names to
+        attributes.
+
+        If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+,
+        :class:`collections.OrderedDict` otherwise), the order is deduced from
+        the order of the names or attributes inside *attrs*.  Otherwise the
+        order of the definition of the attributes is used.
+    :type attrs: :class:`list` or :class:`dict`
+
+    :param tuple bases: Classes that the new class will subclass.
+
+    :param attributes_arguments: Passed unmodified to :func:`attr.s`.
+
+    :return: A new class with *attrs*.
+    :rtype: type
+
+    .. versionadded:: 17.1.0 *bases*
+    .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
+    """
+    if isinstance(attrs, dict):
+        cls_dict = attrs
+    elif isinstance(attrs, (list, tuple)):
+        cls_dict = dict((a, attrib()) for a in attrs)
+    else:
+        raise TypeError("attrs argument must be a dict or a list.")
+
+    post_init = cls_dict.pop("__attrs_post_init__", None)
+    type_ = type(
+        name,
+        bases,
+        {} if post_init is None else {"__attrs_post_init__": post_init}
+    )
+    # For pickling to work, the __module__ variable needs to be set to the
+    # frame where the class is created.  Bypass this step in environments where
+    # sys._getframe is not defined (Jython for example) or sys._getframe is not
+    # defined for arguments greater than 0 (IronPython).
+    try:
+        type_.__module__ = sys._getframe(1).f_globals.get(
+            "__name__", "__main__",
+        )
+    except (AttributeError, ValueError):
+        pass
+
+    return _attrs(these=cls_dict, **attributes_arguments)(type_)
+
+
+# These are required by within this module so we define them here and merely
+# import into .validators.
+
+
+@attrs(slots=True, hash=True)
+class _AndValidator(object):
+    """
+    Compose many validators to a single one.
+    """
+    _validators = attrib()
+
+    def __call__(self, inst, attr, value):
+        for v in self._validators:
+            v(inst, attr, value)
+
+
+def and_(*validators):
+    """
+    A validator that composes multiple validators into one.
+
+    When called on a value, it runs all wrapped validators.
+
+    :param validators: Arbitrary number of validators.
+    :type validators: callables
+
+    .. versionadded:: 17.1.0
+    """
+    vals = []
+    for validator in validators:
+        vals.extend(
+            validator._validators if isinstance(validator, _AndValidator)
+            else [validator]
+        )
+
+    return _AndValidator(tuple(vals))
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/converters.py
@@ -0,0 +1,24 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+def optional(converter):
+    """
+    A converter that allows an attribute to be optional. An optional attribute
+    is one which can be set to ``None``.
+
+    :param callable converter: the converter that is used for non-``None``
+        values.
+
+    ..  versionadded:: 17.1.0
+    """
+
+    def optional_converter(val):
+        if val is None:
+            return None
+        return converter(val)
+
+    return optional_converter
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/exceptions.py
@@ -0,0 +1,48 @@
+from __future__ import absolute_import, division, print_function
+
+
+class FrozenInstanceError(AttributeError):
+    """
+    A frozen/immutable instance has been attempted to be modified.
+
+    It mirrors the behavior of ``namedtuples`` by using the same error message
+    and subclassing :exc:`AttributeError`.
+
+    .. versionadded:: 16.1.0
+    """
+    msg = "can't set attribute"
+    args = [msg]
+
+
+class AttrsAttributeNotFoundError(ValueError):
+    """
+    An ``attrs`` function couldn't find an attribute that the user asked for.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class NotAnAttrsClassError(ValueError):
+    """
+    A non-``attrs`` class has been passed into an ``attrs`` function.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class DefaultAlreadySetError(RuntimeError):
+    """
+    A default has been set using ``attr.ib()`` and is attempted to be reset
+    using the decorator.
+
+    .. versionadded:: 17.1.0
+    """
+
+
+class UnannotatedAttributeError(RuntimeError):
+    """
+    A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
+    annotation.
+
+    .. versionadded:: 17.3.0
+    """
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/filters.py
@@ -0,0 +1,52 @@
+"""
+Commonly useful filters for :func:`attr.asdict`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._compat import isclass
+from ._make import Attribute
+
+
+def _split_what(what):
+    """
+    Returns a tuple of `frozenset`s of classes and attributes.
+    """
+    return (
+        frozenset(cls for cls in what if isclass(cls)),
+        frozenset(cls for cls in what if isinstance(cls, Attribute)),
+    )
+
+
+def include(*what):
+    """
+    Whitelist *what*.
+
+    :param what: What to whitelist.
+    :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\\ s
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def include_(attribute, value):
+        return value.__class__ in cls or attribute in attrs
+
+    return include_
+
+
+def exclude(*what):
+    """
+    Blacklist *what*.
+
+    :param what: What to blacklist.
+    :type what: :class:`list` of classes or :class:`attr.Attribute`\\ s.
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def exclude_(attribute, value):
+        return value.__class__ not in cls and attribute not in attrs
+
+    return exclude_
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/src/attr/validators.py
@@ -0,0 +1,166 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import _AndValidator, and_, attrib, attrs
+
+
+__all__ = [
+    "and_",
+    "in_",
+    "instance_of",
+    "optional",
+    "provides",
+]
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+    type = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not isinstance(value, self.type):
+            raise TypeError(
+                "'{name}' must be {type!r} (got {value!r} that is a "
+                "{actual!r})."
+                .format(name=attr.name, type=self.type,
+                        actual=value.__class__, value=value),
+                attr, self.type, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<instance_of validator for type {type!r}>"
+            .format(type=self.type)
+        )
+
+
+def instance_of(type):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with a wrong type for this particular attribute (checks are performed using
+    :func:`isinstance` therefore it's also valid to pass a tuple of types).
+
+    :param type: The type to check for.
+    :type type: type or tuple of types
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected type, and the value it
+        got.
+    """
+    return _InstanceOfValidator(type)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+    interface = attrib()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.interface.providedBy(value):
+            raise TypeError(
+                "'{name}' must provide {interface!r} which {value!r} "
+                "doesn't."
+                .format(name=attr.name, interface=self.interface, value=value),
+                attr, self.interface, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<provides validator for interface {interface!r}>"
+            .format(interface=self.interface)
+        )
+
+
+def provides(interface):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with an object that does not provide the requested *interface* (checks are
+    performed using ``interface.providedBy(value)`` (see `zope.interface
+    <https://zopeinterface.readthedocs.io/en/latest/>`_).
+
+    :param zope.interface.Interface interface: The interface to check for.
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected interface, and the
+        value it got.
+    """
+    return _ProvidesValidator(interface)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+    validator = attrib()
+
+    def __call__(self, inst, attr, value):
+        if value is None:
+            return
+
+        self.validator(inst, attr, value)
+
+    def __repr__(self):
+        return (
+            "<optional validator for {what} or None>"
+            .format(what=repr(self.validator))
+        )
+
+
+def optional(validator):
+    """
+    A validator that makes an attribute optional.  An optional attribute is one
+    which can be set to ``None`` in addition to satisfying the requirements of
+    the sub-validator.
+
+    :param validator: A validator (or a list of validators) that is used for
+        non-``None`` values.
+    :type validator: callable or :class:`list` of callables.
+
+    .. versionadded:: 15.1.0
+    .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+    """
+    if isinstance(validator, list):
+        return _OptionalValidator(_AndValidator(validator))
+    return _OptionalValidator(validator)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InValidator(object):
+    options = attrib()
+
+    def __call__(self, inst, attr, value):
+        if value not in self.options:
+            raise ValueError(
+                "'{name}' must be in {options!r} (got {value!r})"
+                .format(name=attr.name, options=self.options, value=value)
+            )
+
+    def __repr__(self):
+        return (
+            "<in_ validator with options {options!r}>"
+            .format(options=self.options)
+        )
+
+
+def in_(options):
+    """
+    A validator that raises a :exc:`ValueError` if the initializer is called
+    with a value that does not belong in the options provided.  The check is
+    performed using ``value in options``.
+
+    :param options: Allowed options.
+    :type options: list, tuple, :class:`enum.Enum`, ...
+
+    :raises ValueError: With a human readable error message, the attribute (of
+       type :class:`attr.Attribute`), the expected options, and the value it
+       got.
+
+    .. versionadded:: 17.1.0
+    """
+    return _InValidator(options)
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/tests/strategies.py
@@ -0,0 +1,203 @@
+"""
+Testing strategies for Hypothesis-based tests.
+"""
+
+import keyword
+import string
+
+from collections import OrderedDict
+
+from hypothesis import strategies as st
+
+import attr
+
+from .utils import make_class
+
+
+def gen_attr_names():
+    """
+    Generate names for attributes, 'a'...'z', then 'aa'...'zz'.
+
+    ~702 different attribute names should be enough in practice.
+
+    Some short strings (such as 'as') are keywords, so we skip them.
+    """
+    lc = string.ascii_lowercase
+    for c in lc:
+        yield c
+    for outer in lc:
+        for inner in lc:
+            res = outer + inner
+            if keyword.iskeyword(res):
+                continue
+            yield outer + inner
+
+
+def maybe_underscore_prefix(source):
+    """
+    A generator to sometimes prepend an underscore.
+    """
+    to_underscore = False
+    for val in source:
+        yield val if not to_underscore else '_' + val
+        to_underscore = not to_underscore
+
+
+def _create_hyp_class(attrs):
+    """
+    A helper function for Hypothesis to generate attrs classes.
+    """
+    return make_class(
+        "HypClass", dict(zip(gen_attr_names(), attrs))
+    )
+
+
+def _create_hyp_nested_strategy(simple_class_strategy):
+    """
+    Create a recursive attrs class.
+
+    Given a strategy for building (simpler) classes, create and return
+    a strategy for building classes that have as an attribute: either just
+    the simpler class, a list of simpler classes, a tuple of simpler classes,
+    an ordered dict or a dict mapping the string "cls" to a simpler class.
+    """
+    # Use a tuple strategy to combine simple attributes and an attr class.
+    def just_class(tup):
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=attr.Factory(tup[1])))
+        return _create_hyp_class(combined_attrs)
+
+    def list_of_class(tup):
+        default = attr.Factory(lambda: [tup[1]()])
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    def tuple_of_class(tup):
+        default = attr.Factory(lambda: (tup[1](),))
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    def dict_of_class(tup):
+        default = attr.Factory(lambda: {"cls": tup[1]()})
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    def ordereddict_of_class(tup):
+        default = attr.Factory(lambda: OrderedDict([("cls", tup[1]())]))
+        combined_attrs = list(tup[0])
+        combined_attrs.append(attr.ib(default=default))
+        return _create_hyp_class(combined_attrs)
+
+    # A strategy producing tuples of the form ([list of attributes], <given
+    # class strategy>).
+    attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)
+
+    return st.one_of(attrs_and_classes.map(just_class),
+                     attrs_and_classes.map(list_of_class),
+                     attrs_and_classes.map(tuple_of_class),
+                     attrs_and_classes.map(dict_of_class),
+                     attrs_and_classes.map(ordereddict_of_class))
+
+
+bare_attrs = st.builds(attr.ib, default=st.none())
+int_attrs = st.integers().map(lambda i: attr.ib(default=i))
+str_attrs = st.text().map(lambda s: attr.ib(default=s))
+float_attrs = st.floats().map(lambda f: attr.ib(default=f))
+dict_attrs = (st.dictionaries(keys=st.text(), values=st.integers())
+              .map(lambda d: attr.ib(default=d)))
+
+simple_attrs_without_metadata = (bare_attrs | int_attrs | str_attrs |
+                                 float_attrs | dict_attrs)
+
+
+@st.composite
+def simple_attrs_with_metadata(draw):
+    """
+    Create a simple attribute with arbitrary metadata.
+    """
+    c_attr = draw(simple_attrs)
+    keys = st.booleans() | st.binary() | st.integers() | st.text()
+    vals = st.booleans() | st.binary() | st.integers() | st.text()
+    metadata = draw(st.dictionaries(
+        keys=keys, values=vals, min_size=1, max_size=5))
+
+    return attr.ib(
+        default=c_attr._default,
+        validator=c_attr._validator,
+        repr=c_attr.repr,
+        cmp=c_attr.cmp,
+        hash=c_attr.hash,
+        init=c_attr.init,
+        metadata=metadata,
+        type=None,
+        converter=c_attr.converter,
+    )
+
+
+simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata()
+
+# Python functions support up to 255 arguments.
+list_of_attrs = st.lists(simple_attrs, max_size=9)
+
+
+@st.composite
+def simple_classes(draw, slots=None, frozen=None, private_attrs=None):
+    """
+    A strategy that generates classes with default non-attr attributes.
+
+    For example, this strategy might generate a class such as:
+
+    @attr.s(slots=True, frozen=True)
+    class HypClass:
+        a = attr.ib(default=1)
+        _b = attr.ib(default=None)
+        c = attr.ib(default='text')
+        _d = attr.ib(default=1.0)
+        c = attr.ib(default={'t': 1})
+
+    By default, all combinations of slots and frozen classes will be generated.
+    If `slots=True` is passed in, only slots classes will be generated, and
+    if `slots=False` is passed in, no slot classes will be generated. The same
+    applies to `frozen`.
+
+    By default, some attributes will be private (i.e. prefixed with an
+    underscore). If `private_attrs=True` is passed in, all attributes will be
+    private, and if `private_attrs=False`, no attributes will be private.
+    """
+    attrs = draw(list_of_attrs)
+    frozen_flag = draw(st.booleans()) if frozen is None else frozen
+    slots_flag = draw(st.booleans()) if slots is None else slots
+
+    if private_attrs is None:
+        attr_names = maybe_underscore_prefix(gen_attr_names())
+    elif private_attrs is True:
+        attr_names = ('_' + n for n in gen_attr_names())
+    elif private_attrs is False:
+        attr_names = gen_attr_names()
+
+    cls_dict = dict(zip(attr_names, attrs))
+    post_init_flag = draw(st.booleans())
+    if post_init_flag:
+        def post_init(self):
+            pass
+        cls_dict["__attrs_post_init__"] = post_init
+
+    return make_class(
+        "HypClass",
+        cls_dict,
+        slots=slots_flag,
+        frozen=frozen_flag,
+    )
+
+
+# st.recursive works by taking a base strategy (in this case, simple_classes)
+# and a special function.  This function receives a strategy, and returns
+# another strategy (building on top of the base strategy).
+nested_classes = st.recursive(
+    simple_classes(),
+    _create_hyp_nested_strategy,
+    max_leaves=10
+)
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/tests/test_annotations.py
@@ -0,0 +1,229 @@
+"""
+Tests for PEP-526 type annotations.
+
+Python 3.6+ only.
+"""
+
+import types
+import typing
+
+import pytest
+
+import attr
+
+from attr._make import _classvar_prefixes
+from attr.exceptions import UnannotatedAttributeError
+
+
+class TestAnnotations:
+    """
+    Tests for types derived from variable annotations (PEP-526).
+    """
+
+    def test_basic_annotations(self):
+        """
+        Sets the `Attribute.type` attr from basic type annotations.
+        """
+        @attr.s
+        class C:
+            x: int = attr.ib()
+            y = attr.ib(type=str)
+            z = attr.ib()
+
+        assert int is attr.fields(C).x.type
+        assert str is attr.fields(C).y.type
+        assert None is attr.fields(C).z.type
+        assert C.__init__.__annotations__ == {
+            'x': int,
+            'y': str,
+            'return': None,
+        }
+
+    def test_catches_basic_type_conflict(self):
+        """
+        Raises ValueError if type is specified both ways.
+        """
+        with pytest.raises(ValueError) as e:
+            @attr.s
+            class C:
+                x: int = attr.ib(type=int)
+
+        assert (
+            "Type annotation and type argument cannot both be present",
+        ) == e.value.args
+
+    def test_typing_annotations(self):
+        """
+        Sets the `Attribute.type` attr from typing annotations.
+        """
+        @attr.s
+        class C:
+            x: typing.List[int] = attr.ib()
+            y = attr.ib(type=typing.Optional[str])
+
+        assert typing.List[int] is attr.fields(C).x.type
+        assert typing.Optional[str] is attr.fields(C).y.type
+        assert C.__init__.__annotations__ == {
+            'x': typing.List[int],
+            'y': typing.Optional[str],
+            'return': None,
+        }
+
+    def test_only_attrs_annotations_collected(self):
+        """
+        Annotations that aren't set to an attr.ib are ignored.
+        """
+        @attr.s
+        class C:
+            x: typing.List[int] = attr.ib()
+            y: int
+
+        assert 1 == len(attr.fields(C))
+        assert C.__init__.__annotations__ == {
+            'x': typing.List[int],
+            'return': None,
+        }
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs(self, slots):
+        """
+        If *auto_attribs* is True, bare annotations are collected too.
+        Defaults work and class variables are ignored.
+        """
+        @attr.s(auto_attribs=True, slots=slots)
+        class C:
+            cls_var: typing.ClassVar[int] = 23
+            a: int
+            x: typing.List[int] = attr.Factory(list)
+            y: int = 2
+            z: int = attr.ib(default=3)
+            foo: typing.Any = None
+
+        i = C(42)
+        assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i)
+
+        attr_names = set(a.name for a in C.__attrs_attrs__)
+        assert "a" in attr_names  # just double check that the set works
+        assert "cls_var" not in attr_names
+
+        assert int == attr.fields(C).a.type
+
+        assert attr.Factory(list) == attr.fields(C).x.default
+        assert typing.List[int] == attr.fields(C).x.type
+
+        assert int == attr.fields(C).y.type
+        assert 2 == attr.fields(C).y.default
+
+        assert int == attr.fields(C).z.type
+
+        assert typing.Any == attr.fields(C).foo.type
+
+        # Class body is clean.
+        if slots is False:
+            with pytest.raises(AttributeError):
+                C.y
+
+            assert 2 == i.y
+        else:
+            assert isinstance(C.y, types.MemberDescriptorType)
+
+            i.y = 23
+            assert 23 == i.y
+
+        assert C.__init__.__annotations__ == {
+            'a': int,
+            'x': typing.List[int],
+            'y': int,
+            'z': int,
+            'foo': typing.Any,
+            'return': None,
+        }
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs_unannotated(self, slots):
+        """
+        Unannotated `attr.ib`s raise an error.
+        """
+        with pytest.raises(UnannotatedAttributeError) as e:
+            @attr.s(slots=slots, auto_attribs=True)
+            class C:
+                v = attr.ib()
+                x: int
+                y = attr.ib()
+                z: str
+
+        assert (
+            "The following `attr.ib`s lack a type annotation: v, y.",
+        ) == e.value.args
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs_subclassing(self, slots):
+        """
+        Attributes from super classes are inherited, it doesn't matter if the
+        subclass has annotations or not.
+
+        Ref #291
+        """
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: int = 1
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class B(A):
+            b: int = 2
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class C(A):
+            pass
+
+        assert "B(a=1, b=2)" == repr(B())
+        assert "C(a=1)" == repr(C())
+
+        assert A.__init__.__annotations__ == {
+            'a': int,
+            'return': None,
+        }
+        assert B.__init__.__annotations__ == {
+            'a': int,
+            'b': int,
+            'return': None,
+        }
+        assert C.__init__.__annotations__ == {
+            'a': int,
+            'return': None,
+        }
+
+    def test_converter_annotations(self):
+        """
+        Attributes with converters don't have annotations.
+        """
+
+        @attr.s(auto_attribs=True)
+        class A:
+            a: int = attr.ib(converter=int)
+
+        assert A.__init__.__annotations__ == {'return': None}
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("classvar", _classvar_prefixes)
+    def test_annotations_strings(self, slots, classvar):
+        """
+        String annotations are passed into __init__ as is.
+        """
+        @attr.s(auto_attribs=True, slots=slots)
+        class C:
+            cls_var: classvar + '[int]' = 23
+            a: 'int'
+            x: 'typing.List[int]' = attr.Factory(list)
+            y: 'int' = 2
+            z: 'int' = attr.ib(default=3)
+            foo: 'typing.Any' = None
+
+        assert C.__init__.__annotations__ == {
+            'a': 'int',
+            'x': 'typing.List[int]',
+            'y': 'int',
+            'z': 'int',
+            'foo': 'typing.Any',
+            'return': None,
+        }
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/tests/test_config.py
@@ -0,0 +1,43 @@
+"""
+Tests for `attr._config`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pytest
+
+from attr import _config
+
+
+class TestConfig(object):
+    def test_default(self):
+        """
+        Run validators by default.
+        """
+        assert True is _config._run_validators
+
+    def test_set_run_validators(self):
+        """
+        Sets `_run_validators`.
+        """
+        _config.set_run_validators(False)
+        assert False is _config._run_validators
+        _config.set_run_validators(True)
+        assert True is _config._run_validators
+
+    def test_get_run_validators(self):
+        """
+        Returns `_run_validators`.
+        """
+        _config._run_validators = False
+        assert _config._run_validators is _config.get_run_validators()
+        _config._run_validators = True
+        assert _config._run_validators is _config.get_run_validators()
+
+    def test_wrong_type(self):
+        """
+        Passing anything else than a boolean raises TypeError.
+        """
+        with pytest.raises(TypeError) as e:
+            _config.set_run_validators("False")
+        assert "'run' must be bool." == e.value.args[0]
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/tests/test_converters.py
@@ -0,0 +1,36 @@
+"""
+Tests for `attr.converters`.
+"""
+
+from __future__ import absolute_import
+
+import pytest
+
+from attr.converters import optional
+
+
+class TestOptional(object):
+    """
+    Tests for `optional`.
+    """
+    def test_success_with_type(self):
+        """
+        Wrapped converter is used as usual if value is not None.
+        """
+        c = optional(int)
+        assert c("42") == 42
+
+    def test_success_with_none(self):
+        """
+        Nothing happens if None.
+        """
+        c = optional(int)
+        assert c(None) is None
+
+    def test_fail(self):
+        """
+        Propagates the underlying conversion error when conversion fails.
+        """
+        c = optional(int)
+        with pytest.raises(ValueError):
+            c("not_an_int")
new file mode 100644
--- /dev/null
+++ b/third_party/python/attrs/tests/test_dark_magic.py
@@ -0,0 +1,415 @@
+"""
+End-to-end tests.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+import pickle
+
+import pytest
+import six
+
+from hypothesis import given
+from hypothesis.strategies import booleans
+
+import attr
+
+from attr._compat import TYPE
+from attr._make import NOTHING, Attribute
+from attr.exceptions import FrozenInstanceError
+
+
+@attr.s
+class C1(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+
+@attr.s(slots=True)
+class C1Slots(object):
+    x = attr.ib(validator=attr.validators.instance_of(int))
+    y = attr.ib()
+
+
+foo = None
+
+
+@attr.s()
+class C2(object):
+    x = attr.ib(default=foo)
+    y = attr.ib(default=attr.Factory(list))
+
+
+@attr.s(slots=True)
+class C2Slots(object):
+    x = attr.ib(default=foo)
+    y = attr.ib(default=attr.Factory(list))
+
+
+@attr.s
+class Super(object):
+    x = attr.ib()
+
+    def meth(self):
+        return self.x
+
+
+@attr.s(slots=True)
+class SuperSlots(object):
+    x = attr.ib()
+
+    def meth(self):
+        return self.x
+
+
+@attr.s
+class Sub(Super):
+    y = attr.ib()
+
+
+@attr.s(slots=True)
+class SubSlots(SuperSlots):
+    y = attr.ib()
+
+
+@attr.s(frozen=True, slots=True)
+class Frozen(object):
+    x = attr.ib()
+
+
+@attr.s
+class SubFrozen(Frozen):
+    y = attr.ib()
+
+
+@attr.s(frozen=True, slots=False)
+class FrozenNoSlots(object):
+    x = attr.ib()
+
+
+class Meta(type):
+    pass
+
+
+@attr.s
+@six.add_metaclass(Meta)
+class WithMeta(object):
+    pass
+
+
+@attr.s(slots=True)
+@six.add_metaclass(Meta)
+class WithMetaSlots(object):
+    pass
+
+
+FromMakeClass = attr.make_class("FromMakeClass", ["x"])
+
+
+class TestDarkMagic(object):
+    """
+    Integration tests.
+    """
+    @pytest.mark.parametrize("cls", [C2, C2Slots])
+    def test_fields(self, cls):
+        """
+        `attr.fields` works.
+        """
+        assert (
+            Attribute(name="x", default=foo, validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+            Attribute(name="y", default=attr.Factory(list), validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+        ) == attr.fields(cls)
+
+    @pytest.mark.parametrize("cls", [C1, C1Slots])
+    def test_asdict(self, cls):
+        """
+        `attr.asdict` works.
+        """
+        assert {
+            "x": 1,
+            "y": 2,
+        } == attr.asdict(cls(x=1, y=2))
+
+    @pytest.mark.parametrize("cls", [C1, C1Slots])
+    def test_validator(self, cls):
+        """
+        `instance_of` raises `TypeError` on type mismatch.
+        """
+        with pytest.raises(TypeError) as e:
+            cls("1", 2)
+
+        # Using C1 explicitly, since slot classes don't support this.
+        assert (
+            "'x' must be <{type} 'int'> (got '1' that is a <{type} "
+            "'str'>).".format(type=TYPE),
+            attr.fields(C1).x, int, "1",
+        ) == e.value.args
+
+    @given(booleans())
+    def test_renaming(self, slots):
+        """
+        Private members are renamed but only in `__init__`.
+        """
+        @attr.s(slots=slots)
+        class C3(object):
+            _x = attr.ib()
+
+        assert "C3(_x=1)" == repr(C3(x=1))
+
+    @given(booleans(), booleans())
+    def test_programmatic(self, slots, frozen):
+        """
+        `attr.make_class` works.
+        """
+        PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen)
+        assert (
+            Attribute(name="a", default=NOTHING, validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+            Attribute(name="b", default=NOTHING, validator=None,
+                      repr=True, cmp=True, hash=None, init=True),
+        ) == attr.fields(PC)
+
+    @pytest.mark.parametrize("cls", [Sub, SubSlots])
+    def test_subclassing_with_extra_attrs(self, cls):
+        """
+        Sub-classing (where the subclass has extra attrs) does what you'd hope
+        for.
+        """
+        obj = object()
+        i = cls(x=obj, y=2)
+        assert i.x is i.meth() is obj
+        assert i.y == 2
+        if cls is Sub:
+            assert "Sub(x={obj}, y=2)".format(obj=obj) == repr(i)
+        else:
+            assert "SubSlots(x={obj}, y=2)".format(obj=obj) == repr(i)
+
+    @pytest.mark.parametrize("base", [Super, SuperSlots])
+    def test_subclass_without_extra_attrs(self, base):
+        """
+        Sub-classing (where the subclass does not have extra attrs) still
+        behaves the same as a subclass with extra attrs.
+        """
+        class Sub2(base):
+            pass
+
+        obj = object()
+        i = Sub2(x=obj)
+        assert i.x is i.meth() is obj
+        assert "Sub2(x={obj})".format(obj=obj) == repr(i)
+
+    @pytest.mark.parametrize("frozen_class", [
+        Frozen,  # has slots=True
+        attr.make_class("FrozenToo", ["x"], slots=False, frozen=True),
+    ])
+    def test_frozen_instance(self, frozen_class):
+        """
+        Frozen instances can't be modified (easily).
+        """
+        frozen = frozen_class(1)
+
+        with pytest.raises(FrozenInstanceError) as e:
+            frozen.x = 2
+
+        with pytest.raises(FrozenInstanceError) as e:
+            del frozen.x
+
+        assert e.value.args[0] == "can't set attribute"
+        assert 1 == frozen.x
+
+    @pytest.mark.parametrize("cls",
+                             [C1, C1Slots, C2, C2Slots, Super, SuperSlots,
+                              Sub, SubSlots, Frozen, FrozenNoSlots,
+                              FromMakeClass])
+    @pytest.mark.parametrize("protocol",
+                             range(2, pickle.HIGHEST_PROTOCOL + 1))
+    def test_pickle_attributes(self, cls, protocol):
+        """
+        Pickling/un-pickling of Attribute instances works.
+        """
+        for attribute in attr.fields(cls):
+            assert attribute == pickle.loads(pickle.dumps(attribute, protocol))
+
+    @pytest.mark.parametrize("cls",
+                             [C1, C1Slots, C2, C2Slots, Super, SuperSlots,
+                              Sub, SubSlots, Frozen, FrozenNoSlots,
+                              FromMakeClass])
+    @pytest.mark.parametrize("protocol",
+                             range(2, pickle.HIGHEST_PROTOCOL + 1))
+    def test_pickle_object(self, cls, protocol):
+        """
+        Pickle object serialization works on all kinds of attrs classes.
+        """
+        if len(attr.fields(cls)) == 2:
+            obj = cls(123, 456)
+        else:
+            obj = cls(123)
+        assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol)))
+
+    def test_subclassing_frozen_gives_frozen(self):
+        """
+        The frozen-ness of classes is inherited.  Subclasses of frozen classes
+        are also frozen and can be instantiated.
+        """
+        i = SubFrozen("foo", "bar")
+
+        assert i.x == "foo"
+        assert i.y == "bar"
+
+    @pytest.mark.parametrize("cls", [WithMeta, WithMetaSlots])
+    def test_metaclass_preserved(self, cls):
+        """
+        Metaclass data is preserved.
+        """
+        assert Meta == type(cls)
+
+    def test_default_decorator(self):
+        """
+        Default decorator sets the default and the respective method gets
+        called.
+        """
+        @attr.s
+        class C(object):
+            x = attr.ib(default=1)
+            y = attr.ib()
+
+            @y.default
+            def compute(self):
+                return self.x + 1
+
+        assert C(1, 2) == C()
+
+    @pytest.mark.parametrize("slots", [True, False])
+    @pytest.mark.parametrize("frozen", [True, False])
+    def test_attrib_overwrite(self, slots, frozen):
+        """
+        Subclasses can overwrite attributes of their superclass.
+        """
+        @attr.s(slots=slots, frozen=frozen)
+        class SubOverwrite(Super):
+            x = attr.ib(default=attr.Factory(list))
+
+        assert SubOverwrite([]) == SubOverwrite()
+
+    def test_dict_patch_class(self):
+        """
+        dict-classes are never replaced.
+        """
+        class C(object):
+            x = attr.ib()
+
+        C_new = attr.s(C)
+
+        assert C_new is C
+
+    def test_hash_by_id(self):
+        """
+        With dict classes, hashing by ID is active for hash=False even on
+        Python 3.  This is incorrect behavior but we have to retain it for
+        backward compatibility.
+        """
+        @attr.s(hash=False)
+        class HashByIDBackwardCompat(object):
+            x = attr.ib()
+
+        assert (
+            hash(HashByIDBackwardCompat(1)) != hash(HashByIDBackwardCompat(1))
+        )
+
+        @attr.s(hash=False, cmp=False)
+        class HashByID(object):
+            x = attr.ib()
+
+        assert hash(HashByID(1)) != hash(HashByID(1))
+
+        @attr.s(hash=True)
+        class HashByValues(object):
+            x = attr.ib()
+
+        assert hash(HashByValues(1)) == hash(HashByValues(1))
+
+    def test_handles_different_defaults(self):
+        """
+        Unhashable defaults + subclassing values work.
+        """
+        @attr.s
+        class Unhashable(object):
+            pass
+
+        @attr.s
+        class C(object):
+            x = attr.ib(default=Unhashable())
+
+        @attr.s
+        class D(C):
+            pass
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_hash_false_cmp_false(self, slots):
+        """
+        hash=False and cmp=False make a class hashable by ID.
+        """
+        @attr.s(hash=False, cmp=False, slots=slots)
+        class C(object):
+            pass
+
+        assert hash(C()) != hash(C())
+
+    def test_overwrite_super(self):
+        """
+        Super classes can overwrite each other and the attributes are added
+        in the order they are defined.
+        """
+        @attr.s
+        class C(object):
+            c = attr.ib(default=100)
+            x = attr.ib(default=1)
+            b = attr.ib(default=23)
+
+        @attr.s
+        class D(C):
+            a = attr.ib(default=42)
+            x = attr.ib(default=2)
+            d = attr.ib(default=3.14)
+
+        @attr.s
+        class E(D):
+            y = attr.ib(default=3)
+            z = attr.ib(default=4)
+
+        assert "E(c=100, b=23, a=42, x=2, d=3.14, y=3, z=4)" == repr(E())
+
+    @pytest.mark.parametrize("base_slots", [True, False])
+    @pytest.mark.parametrize("sub_slots", [True, False])
+    @pytest.mark.parametrize("base_frozen", [True, False])
+    @pytest.mark.parametrize("sub_frozen", [True, False])
<