prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Fractional memory / CPU limits / guarantees fail
  **How to reproduce the issue**
  
  Set memory limit (or guarantee, or cpu limit / guarantee) to a non-integral spec:
  
  ```python
  c.Spawner.mem_limit = "1.5G"
  ```
  
  **What you expected to happen**
  
  (In supported spawners) memory limit is set to 1.5 gigabytes of RAM
  
  **What actually happens**
  
  JupyterHub refuses to start, with:
  
  ```
  [E 2017-04-18 05:39:02.270 JupyterHub app:1527]
      Traceback (most recent call last):
        File "/usr/local/lib/python3.4/dist-packages/jupyterhub/app.py", line 1524, in launch_instance_async
          yield self.initialize(argv)
        File "/usr/local/lib/python3.4/dist-packages/jupyterhub/app.py", line 1315, in initialize
          yield self.init_spawners()
        File "/usr/local/lib/python3.4/dist-packages/jupyterhub/app.py", line 1084, in init_spawners
          self.users[orm_user.id] = user = User(orm_user, self.tornado_settings)
        File "/usr/local/lib/python3.4/dist-packages/jupyterhub/user.py", line 128, in __init__
          config=self.settings.get('config'),
        File "/usr/local/lib/python3.4/dist-packages/kubespawner/spawner.py", line 29, in __init__
          super().__init__(*args, **kwargs)
        File "/usr/local/lib/python3.4/dist-packages/jupyterhub/spawner.py", line 345, in __init__
          super(Spawner, self).__init__(**kwargs)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/config/configurable.py", line 84, in __init__
          self.config = config
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 585, in __set__
          self.set(obj, value)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 574, in set
          obj._notify_trait(self.name, old_value, new_value)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 1139, in _notify_trait
          type='change',
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 1176, in notify_change
          c(change)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 819, in compatible_observer
          return func(self, change)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/config/configurable.py", line 186, in _config_changed
          self._load_config(change.new, traits=traits, section_names=section_names)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/config/configurable.py", line 153, in _load_config
          setattr(self, name, deepcopy(config_value))
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 585, in __set__
          self.set(obj, value)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 559, in set
          new_value = self._validate(obj, value)
        File "/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py", line 591, in _validate
          value = self.validate(obj, value)
        File "/usr/local/lib/python3.4/dist-packages/jupyterhub/traitlets.py", line 71, in validate
          return int(num) * ByteSpecification.UNIT_SUFFIXES[suffix]
      ValueError: invalid literal for int() with base 10: '1.5'
  ```
  
  **Share what version of JupyterHub you are using**
  
  0.72.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `jupyterhub/traitlets.py`
  Content:
  ```
  1 """
  2 Traitlets that are used in JupyterHub
  3 """
  4 # Copyright (c) Jupyter Development Team.
  5 # Distributed under the terms of the Modified BSD License.
  6 
  7 from traitlets import List, Unicode, Integer, TraitError
  8 
  9 
  10 class URLPrefix(Unicode):
  11     def validate(self, obj, value):
  12         u = super().validate(obj, value)
  13         if not u.startswith('/'):
  14             u = '/' + u
  15         if not u.endswith('/'):
  16             u = u + '/'
  17         return u
  18 
  19 
  20 class Command(List):
  21     """Traitlet for a command that should be a list of strings,
  22     but allows it to be specified as a single string.
  23     """
  24     def __init__(self, default_value=None, **kwargs):
  25         kwargs.setdefault('minlen', 1)
  26         if isinstance(default_value, str):
  27             default_value = [default_value]
  28         super().__init__(Unicode(), default_value, **kwargs)
  29 
  30     def validate(self, obj, value):
  31         if isinstance(value, str):
  32             value = [value]
  33         return super().validate(obj, value)
  34 
  35 
  36 class ByteSpecification(Integer):
  37     """
  38     Allow easily specifying bytes in units of 1024 with suffixes
  39 
  40     Suffixes allowed are:
  41       - K -> Kilobyte
  42       - M -> Megabyte
  43       - G -> Gigabyte
  44       - T -> Terabyte
  45     """
  46 
  47     UNIT_SUFFIXES = {
  48         'K': 1024,
  49         'M': 1024 * 1024,
  50         'G': 1024 * 1024 * 1024,
  51         'T': 1024 * 1024 * 1024 * 1024
  52     }
  53 
  54     # Default to allowing None as a value
  55     allow_none = True
  56 
  57     def validate(self, obj, value):
  58         """
  59         Validate that the passed in value is a valid memory specification
  60 
  61         It could either be a pure int, when it is taken as a byte value.
  62         If it has one of the suffixes, it is converted into the appropriate
  63         pure byte value.
  64         """
  65         if isinstance(value, int):
  66             return value
  67         num = value[:-1]
  68         suffix = value[-1]
  69         if not num.isdigit() and suffix not in ByteSpecification.UNIT_SUFFIXES:
  70             raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))
  71         else:
  72             return int(num) * ByteSpecification.UNIT_SUFFIXES[suffix]
  73
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/jupyterhub/traitlets.py b/jupyterhub/traitlets.py
  --- a/jupyterhub/traitlets.py
  +++ b/jupyterhub/traitlets.py
  @@ -48,7 +48,7 @@
           'K': 1024,
           'M': 1024 * 1024,
           'G': 1024 * 1024 * 1024,
  -        'T': 1024 * 1024 * 1024 * 1024
  +        'T': 1024 * 1024 * 1024 * 1024,
       }
   
       # Default to allowing None as a value
  @@ -62,11 +62,15 @@
           If it has one of the suffixes, it is converted into the appropriate
           pure byte value.
           """
  -        if isinstance(value, int):
  -            return value
  -        num = value[:-1]
  +        if isinstance(value, (int, float)):
  +            return int(value)
  +
  +        try:
  +            num = float(value[:-1])
  +        except ValueError:
  +            raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))
           suffix = value[-1]
  -        if not num.isdigit() and suffix not in ByteSpecification.UNIT_SUFFIXES:
  +        if suffix not in self.UNIT_SUFFIXES:
               raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))
           else:
  -            return int(num) * ByteSpecification.UNIT_SUFFIXES[suffix]
  +            return int(float(num) * self.UNIT_SUFFIXES[suffix])
  
verification_info: {"golden_diff": "diff --git a/jupyterhub/traitlets.py b/jupyterhub/traitlets.py\n--- a/jupyterhub/traitlets.py\n+++ b/jupyterhub/traitlets.py\n@@ -48,7 +48,7 @@\n         'K': 1024,\n         'M': 1024 * 1024,\n         'G': 1024 * 1024 * 1024,\n-        'T': 1024 * 1024 * 1024 * 1024\n+        'T': 1024 * 1024 * 1024 * 1024,\n     }\n \n     # Default to allowing None as a value\n@@ -62,11 +62,15 @@\n         If it has one of the suffixes, it is converted into the appropriate\n         pure byte value.\n         \"\"\"\n-        if isinstance(value, int):\n-            return value\n-        num = value[:-1]\n+        if isinstance(value, (int, float)):\n+            return int(value)\n+\n+        try:\n+            num = float(value[:-1])\n+        except ValueError:\n+            raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))\n         suffix = value[-1]\n-        if not num.isdigit() and suffix not in ByteSpecification.UNIT_SUFFIXES:\n+        if suffix not in self.UNIT_SUFFIXES:\n             raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))\n         else:\n-            return int(num) * ByteSpecification.UNIT_SUFFIXES[suffix]\n+            return int(float(num) * self.UNIT_SUFFIXES[suffix])\n", "issue": "Fractional memory / CPU limits / guarantees fail\n**How to reproduce the issue**\r\n\r\nSet memory limit (or guarantee, or cpu limit / guarantee) to a non-integral spec:\r\n\r\n```python\r\nc.Spawner.mem_limit = \"1.5G\"\r\n```\r\n\r\n**What you expected to happen**\r\n\r\n(In supported spawners) memory limit is set to 1.5 gigabytes of RAM\r\n\r\n**What actually happens**\r\n\r\nJupyterHub refuses to start, with:\r\n\r\n```\r\n[E 2017-04-18 05:39:02.270 JupyterHub app:1527]\r\n    Traceback (most recent call last):\r\n      File \"/usr/local/lib/python3.4/dist-packages/jupyterhub/app.py\", line 1524, in launch_instance_async\r\n        yield self.initialize(argv)\r\n      File \"/usr/local/lib/python3.4/dist-packages/jupyterhub/app.py\", line 1315, in initialize\r\n        yield self.init_spawners()\r\n      File \"/usr/local/lib/python3.4/dist-packages/jupyterhub/app.py\", line 1084, in init_spawners\r\n        self.users[orm_user.id] = user = User(orm_user, self.tornado_settings)\r\n      File \"/usr/local/lib/python3.4/dist-packages/jupyterhub/user.py\", line 128, in __init__\r\n        config=self.settings.get('config'),\r\n      File \"/usr/local/lib/python3.4/dist-packages/kubespawner/spawner.py\", line 29, in __init__\r\n        super().__init__(*args, **kwargs)\r\n      File \"/usr/local/lib/python3.4/dist-packages/jupyterhub/spawner.py\", line 345, in __init__\r\n        super(Spawner, self).__init__(**kwargs)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/config/configurable.py\", line 84, in __init__\r\n        self.config = config\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 585, in __set__\r\n        self.set(obj, value)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 574, in set\r\n        obj._notify_trait(self.name, old_value, new_value)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 1139, in _notify_trait\r\n        type='change',\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 1176, in notify_change\r\n        c(change)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 819, in compatible_observer\r\n        return func(self, change)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/config/configurable.py\", line 186, in _config_changed\r\n        self._load_config(change.new, traits=traits, section_names=section_names)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/config/configurable.py\", line 153, in _load_config\r\n        setattr(self, name, deepcopy(config_value))\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 585, in __set__\r\n        self.set(obj, value)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 559, in set\r\n        new_value = self._validate(obj, value)\r\n      File \"/usr/local/lib/python3.4/dist-packages/traitlets/traitlets.py\", line 591, in _validate\r\n        value = self.validate(obj, value)\r\n      File \"/usr/local/lib/python3.4/dist-packages/jupyterhub/traitlets.py\", line 71, in validate\r\n        return int(num) * ByteSpecification.UNIT_SUFFIXES[suffix]\r\n    ValueError: invalid literal for int() with base 10: '1.5'\r\n```\r\n\r\n**Share what version of JupyterHub you are using**\r\n\r\n0.72.\n", "before_files": [{"content": "\"\"\"\nTraitlets that are used in JupyterHub\n\"\"\"\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nfrom traitlets import List, Unicode, Integer, TraitError\n\n\nclass URLPrefix(Unicode):\n    def validate(self, obj, value):\n        u = super().validate(obj, value)\n        if not u.startswith('/'):\n            u = '/' + u\n        if not u.endswith('/'):\n            u = u + '/'\n        return u\n\n\nclass Command(List):\n    \"\"\"Traitlet for a command that should be a list of strings,\n    but allows it to be specified as a single string.\n    \"\"\"\n    def __init__(self, default_value=None, **kwargs):\n        kwargs.setdefault('minlen', 1)\n        if isinstance(default_value, str):\n            default_value = [default_value]\n        super().__init__(Unicode(), default_value, **kwargs)\n\n    def validate(self, obj, value):\n        if isinstance(value, str):\n            value = [value]\n        return super().validate(obj, value)\n\n\nclass ByteSpecification(Integer):\n    \"\"\"\n    Allow easily specifying bytes in units of 1024 with suffixes\n\n    Suffixes allowed are:\n      - K -> Kilobyte\n      - M -> Megabyte\n      - G -> Gigabyte\n      - T -> Terabyte\n    \"\"\"\n\n    UNIT_SUFFIXES = {\n        'K': 1024,\n        'M': 1024 * 1024,\n        'G': 1024 * 1024 * 1024,\n        'T': 1024 * 1024 * 1024 * 1024\n    }\n\n    # Default to allowing None as a value\n    allow_none = True\n\n    def validate(self, obj, value):\n        \"\"\"\n        Validate that the passed in value is a valid memory specification\n\n        It could either be a pure int, when it is taken as a byte value.\n        If it has one of the suffixes, it is converted into the appropriate\n        pure byte value.\n        \"\"\"\n        if isinstance(value, int):\n            return value\n        num = value[:-1]\n        suffix = value[-1]\n        if not num.isdigit() and suffix not in ByteSpecification.UNIT_SUFFIXES:\n            raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))\n        else:\n            return int(num) * ByteSpecification.UNIT_SUFFIXES[suffix]\n", "path": "jupyterhub/traitlets.py"}], "after_files": [{"content": "\"\"\"\nTraitlets that are used in JupyterHub\n\"\"\"\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nfrom traitlets import List, Unicode, Integer, TraitError\n\n\nclass URLPrefix(Unicode):\n    def validate(self, obj, value):\n        u = super().validate(obj, value)\n        if not u.startswith('/'):\n            u = '/' + u\n        if not u.endswith('/'):\n            u = u + '/'\n        return u\n\n\nclass Command(List):\n    \"\"\"Traitlet for a command that should be a list of strings,\n    but allows it to be specified as a single string.\n    \"\"\"\n    def __init__(self, default_value=None, **kwargs):\n        kwargs.setdefault('minlen', 1)\n        if isinstance(default_value, str):\n            default_value = [default_value]\n        super().__init__(Unicode(), default_value, **kwargs)\n\n    def validate(self, obj, value):\n        if isinstance(value, str):\n            value = [value]\n        return super().validate(obj, value)\n\n\nclass ByteSpecification(Integer):\n    \"\"\"\n    Allow easily specifying bytes in units of 1024 with suffixes\n\n    Suffixes allowed are:\n      - K -> Kilobyte\n      - M -> Megabyte\n      - G -> Gigabyte\n      - T -> Terabyte\n    \"\"\"\n\n    UNIT_SUFFIXES = {\n        'K': 1024,\n        'M': 1024 * 1024,\n        'G': 1024 * 1024 * 1024,\n        'T': 1024 * 1024 * 1024 * 1024,\n    }\n\n    # Default to allowing None as a value\n    allow_none = True\n\n    def validate(self, obj, value):\n        \"\"\"\n        Validate that the passed in value is a valid memory specification\n\n        It could either be a pure int, when it is taken as a byte value.\n        If it has one of the suffixes, it is converted into the appropriate\n        pure byte value.\n        \"\"\"\n        if isinstance(value, (int, float)):\n            return int(value)\n\n        try:\n            num = float(value[:-1])\n        except ValueError:\n            raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))\n        suffix = value[-1]\n        if suffix not in self.UNIT_SUFFIXES:\n            raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))\n        else:\n            return int(float(num) * self.UNIT_SUFFIXES[suffix])\n", "path": "jupyterhub/traitlets.py"}]}