Repository: plotly/dash
File: dash/dash.py (Python)

Code before:
        self._generate_scripts_html()
        self._generate_css_dist_html()

    def _walk_assets_directory(self):
        walk_dir = self._assets_folder
        slash_splitter = re.compile(r'[\\/]+')

        def add_resource(p):
            res = {'asset_path': p}
            if self.config.assets_external_path:
                res['external_url'] = '{}{}'.format(
                    self.config.assets_external_path, path)
            return res

        for current, _, files in os.walk(walk_dir):
            if current == walk_dir:
                base = ''
            else:
                s = current.replace(walk_dir, '').lstrip('\\').lstrip('/')
                splitted = slash_splitter.split(s)
                if len(splitted) > 1:
                    base = '/'.join(slash_splitter.split(s))
                else:
                    base = splitted[0]

            for f in files:
                if base:
                    path = '/'.join([base, f])
                else:
                    path = f

                if f.endswith('js'):
                    self.scripts.append_script(add_resource(path))
                elif f.endswith('css'):
                    self.css.append_css(add_resource(path))
                elif f == 'favicon.ico':
                    self._favicon = path

    def run_server(self,
                   port=8050,
                   debug=False,
                   **flask_run_options):
        self.server.run(port=port, debug=debug, **flask_run_options)


Review comment (suggestion):

`os.walk` calls `os.listdir` internally and the `os.listdir` docs [state](https://docs.python.org/3.6/library/os.html#os.listdir) "The [returned] list is in arbitrary order" since the user's OS ultimately decides what ordering this returns. Definitely not desirable as apps will behave differently on different machines.

I had a hunch the test case was just getting lucky so I added more files and got this ordering: `['load_first', 'load_after6', 'load_after', 'load_after1', 'load_after2', 'load_after5', 'load_after11', 'load_after4', 'load_after3', 'load_after10', 'load_
after7']`

This can be fixed by just adding
```
for current, _, files in os.walk(walk_dir):
            if current == walk_dir:
                base = ''
            else:
                s = current.replace(walk_dir, '').lstrip('\\').lstrip('/')
                splitted = slash_splitter.split(s)
                if len(splitted) > 1:
                    base = '/'.join(slash_splitter.split(s))
                else:
                    base = splitted[0]
            files.sort()  # ADDED LINE: Sort the files!
            for f in files:
                if base:
                    path = '/'.join([base, f])
                else:
                    path = f

```

This sorts the files as: `['load_first', 'load_after', 'load_after1', 'load_after10', 'load_after11', 'load_after2', 'load_after3', 'load_after4', 'load_after5', 'load_after6', 'load_
after7']`

I personally would prefer if 'load_after10' and 'load_after11' came last, which can be done by changing
`files.sort() -> files.sort(key=lambda f: int('0' + ''.join(filter(str.isdigit, f))))`

The former is what is expected, but the latter is what a programmer usually wants when sorting files. Either way I like the way everything else looks and am :dancer: once the sorting works.

Code after:
        # Make sure `layout` is set before running the server
        value = getattr(self, 'layout')
        if value is None:
            raise exceptions.NoLayoutException(
                ''
                'The layout was `None` '
                'at the time that `run_server` was called. '
                'Make sure to set the `layout` attribute of your application '
                'before running the server.')

        self._generate_scripts_html()
        self._generate_css_dist_html()

    def _walk_assets_directory(self):
        walk_dir = self._assets_folder
        slash_splitter = re.compile(r'[\\/]+')

        def add_resource(p):
            res = {'asset_path': p}
            if self.config.assets_external_path:
                res['external_url'] = '{}{}'.format(
                    self.config.assets_external_path, path)
            return res

        for current, _, files in os.walk(walk_dir):
            if current == walk_dir:
                base = ''
            else:
                s = current.replace(walk_dir, '').lstrip('\\').lstrip('/')
                splitted = slash_splitter.split(s)
                if len(splitted) > 1:
                    base = '/'.join(slash_splitter.split(s))
                else:
                    base = splitted[0]

            for f in sorted(files):
                if base:
                    path = '/'.join([base, f])
                else:
                    path = f

                if f.endswith('js'):
                    self.scripts.append_script(add_resource(path))
                elif f.endswith('css'):
                    self.css.append_css(add_resource(path))
                elif f == 'favicon.ico':
                    self._favicon = path

    def run_server(self,
                   port=8050,
                   debug=False,