codecov-io: ## [Current coverage](https://codecov.io/gh/scrapy/scrapy/pull/2464?src=pr) is 83.46% (diff: 100%)
> Merging [#2464](https://codecov.io/gh/scrapy/scrapy/pull/2464?src=pr) into [master](https://codecov.io/gh/scrapy/scrapy/branch/master?src=pr) will increase coverage by **0.01%**

```diff
@@             master      #2464   diff @@
==========================================
  Files           161        161          
  Lines          8779       8787     +8   
  Methods           0          0          
  Messages          0          0          
  Branches       1288       1291     +3   
==========================================
+ Hits           7326       7334     +8   
  Misses         1205       1205          
  Partials        248        248          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [cc06b6b...f83a45f](https://codecov.io/gh/scrapy/scrapy/compare/cc06b6b1f68d33c1a5e5429d6dfb9b39577624fe...f83a45fa5ffff797601c0e1b20acec3fd4caea1d?src=pr)
kmike: Did float priorities work before this change? They look useful.
elacuesta: They worked, and now that I took a closer look the documentation doesn't actually say that the numbers should be integers. I'll change it to allow floats too, thanks for noticing @kmike!
elacuesta: @redapple, @kmike The branch is still named `component_order_integer`, would you guys like me to close this PR and open a new one with a more suitable branch name?
redapple: @elacuesta , that branch name is not an issue, don't worry
redapple: Still looks good to me.
@dangra , @kmike , @vshlapakov, what do you think?
kmike: This looks good! 

May I ask for the last change? :) `_ensure_numeric_value` name is a bit misleading; could we name it `_ensure_numeric_values` or `_raise_if_unsupported_values` or `_validate_values`, something like that? `value` -> `values` because we're working with dict values; `ensure` -> `validate / ...` because code no longer converts strings to numbers.
elacuesta: @kmike Updated, I went with `_validate_numeric_values` :-)
vshlapakov: Looks good, thanks @elacuesta! 
kmike: Hey @elacuesta! Sorry for being so picky. I'm not a native speaker, but for me `_validate_numeric_values` reads as "validate values if they are numeric", i.e. "check only numeric values, but not other values - e.g. ensure that they are positive", not "check that all values are numeric" :)
diff --git a/scrapy/utils/conf.py b/scrapy/utils/conf.py
index e8af90f11..435e9a6b3 100644
--- a/scrapy/utils/conf.py
+++ b/scrapy/utils/conf.py
@@ -1,5 +1,6 @@
 import os
 import sys
+import numbers
 from operator import itemgetter
 
 import six
@@ -34,6 +35,13 @@ def build_component_list(compdict, custom=None, convert=update_classpath):
             _check_components(compdict)
             return {convert(k): v for k, v in six.iteritems(compdict)}
 
+    def _validate_values(compdict):
+        """Fail if a value in the components dict is not a real number or None."""
+        for name, value in six.iteritems(compdict):
+            if value is not None and not isinstance(value, numbers.Real):
+                raise ValueError('Invalid value {} for component {}, please provide ' \
+                                 'a real number or None instead'.format(value, name))
+
     # BEGIN Backwards compatibility for old (base, custom) call signature
     if isinstance(custom, (list, tuple)):
         _check_components(custom)
@@ -43,6 +51,7 @@ def build_component_list(compdict, custom=None, convert=update_classpath):
         compdict.update(custom)
     # END Backwards compatibility
 
+    _validate_values(compdict)
     compdict = without_none_values(_map_keys(compdict))
     return [k for k, v in sorted(six.iteritems(compdict), key=itemgetter(1))]
 

String value for order of Scrapy component
If Scrapy component order is defined as a string, it leads to undefined behaviour on Python 2 and to the following errors on Python 3:
```
File "/usr/local/lib/python3.5/site-packages/scrapy/middleware.py", line 58, in from_crawler
 return cls.from_settings(crawler.settings, crawler)
File "/usr/local/lib/python3.5/site-packages/scrapy/middleware.py", line 29, in from_settings
 mwlist = cls._get_mwlist_from_settings(settings)
File "/usr/local/lib/python3.5/site-packages/scrapy/core/spidermw.py", line 21, in _get_mwlist_from_settings
 return build_component_list(settings.getwithbase('SPIDER_MIDDLEWARES'))
File "/usr/local/lib/python3.5/site-packages/scrapy/utils/conf.py", line 47, in build_component_list
 return [k for k, v in sorted(six.iteritems(compdict), key=itemgetter(1))]
builtins.TypeError: unorderable types: str() < int()
```

My guess that 1) order of a Scrapy component should be stated as of integer type (or `None`) and there should be a check somewhere, 2) or the sorting logic should be fixed.
diff --git a/tests/test_utils_conf.py b/tests/test_utils_conf.py
index dab41ac8d..f203c32ef 100644
--- a/tests/test_utils_conf.py
+++ b/tests/test_utils_conf.py
@@ -62,6 +62,27 @@ class BuildComponentListTest(unittest.TestCase):
         self.assertRaises(ValueError, build_component_list, duplicate_bs,
                           convert=lambda x: x.lower())
 
+    def test_valid_numbers(self):
+        # work well with None and numeric values
+        d = {'a': 10, 'b': None, 'c': 15, 'd': 5.0}
+        self.assertEqual(build_component_list(d, convert=lambda x: x),
+                         ['d', 'a', 'c'])
+        d = {'a': 33333333333333333333, 'b': 11111111111111111111, 'c': 22222222222222222222}
+        self.assertEqual(build_component_list(d, convert=lambda x: x),
+                         ['b', 'c', 'a'])
+        # raise exception for invalid values
+        d = {'one': '5'}
+        self.assertRaises(ValueError, build_component_list, {}, d, convert=lambda x: x)
+        d = {'one': '1.0'}
+        self.assertRaises(ValueError, build_component_list, {}, d, convert=lambda x: x)
+        d = {'one': [1, 2, 3]}
+        self.assertRaises(ValueError, build_component_list, {}, d, convert=lambda x: x)
+        d = {'one': {'a': 'a', 'b': 2}}
+        self.assertRaises(ValueError, build_component_list, {}, d, convert=lambda x: x)
+        d = {'one': 'lorem ipsum',}
+        self.assertRaises(ValueError, build_component_list, {}, d, convert=lambda x: x)
+
+
 
 class UtilsConfTestCase(unittest.TestCase):
 

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==25.3.0
Automat==24.8.1
cffi==1.17.1
constantly==23.10.4
cryptography==44.0.2
cssselect==1.3.0
exceptiongroup==1.2.2
hyperlink==21.0.0
idna==3.10
incremental==24.7.2
iniconfig==2.1.0
jmespath==1.0.1
lxml==5.3.1
packaging==24.2
parsel==1.10.0
pluggy==1.5.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
PyDispatcher==2.0.7
pyOpenSSL==25.0.0
pytest==8.3.5
queuelib==1.7.0
-e git+https://github.com/scrapy/scrapy.git@3b8e6d4d820bf69ed3ad2e4d57eb6ca386323a5f#egg=Scrapy
service-identity==24.2.0
six==1.17.0
tomli==2.2.1
Twisted==24.11.0
typing_extensions==4.13.0
w3lib==2.3.1
zope.interface==7.2

name: scrapy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==25.3.0
      - automat==24.8.1
      - cffi==1.17.1
      - constantly==23.10.4
      - cryptography==44.0.2
      - cssselect==1.3.0
      - exceptiongroup==1.2.2
      - hyperlink==21.0.0
      - idna==3.10
      - incremental==24.7.2
      - iniconfig==2.1.0
      - jmespath==1.0.1
      - lxml==5.3.1
      - packaging==24.2
      - parsel==1.10.0
      - pluggy==1.5.0
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pycparser==2.22
      - pydispatcher==2.0.7
      - pyopenssl==25.0.0
      - pytest==8.3.5
      - queuelib==1.7.0
      - service-identity==24.2.0
      - six==1.17.0
      - tomli==2.2.1
      - twisted==24.11.0
      - typing-extensions==4.13.0
      - w3lib==2.3.1
      - zope-interface==7.2
prefix: /opt/conda/envs/scrapy

tests/test_utils_conf.py::BuildComponentListTest::test_valid_numbers
tests/test_utils_conf.py::BuildComponentListTest::test_backwards_compatible_build_dict tests/test_utils_conf.py::BuildComponentListTest::test_build_dict tests/test_utils_conf.py::BuildComponentListTest::test_duplicate_components_in_basesettings tests/test_utils_conf.py::BuildComponentListTest::test_duplicate_components_in_dict tests/test_utils_conf.py::BuildComponentListTest::test_duplicate_components_in_list tests/test_utils_conf.py::BuildComponentListTest::test_map_dict tests/test_utils_conf.py::BuildComponentListTest::test_map_list tests/test_utils_conf.py::BuildComponentListTest::test_return_list tests/test_utils_conf.py::UtilsConfTestCase::test_arglist_to_dict
scrapy/utils/conf.py
scrapy/utils/conf.py