From bb74478af92ec817d5be57c2337ba6f990160736 Mon Sep 17 00:00:00 2001 From: masklinn Date: Wed, 27 Mar 2024 18:51:25 +0100 Subject: [PATCH] Add migration documentation for 0.x -> 1.0 Fixes #181 --- doc/advanced/migration.rst | 131 +++++++++++++++++++++++++++++++++++++ doc/api.rst | 4 +- doc/guides.rst | 4 ++ doc/index.rst | 1 + 4 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 doc/advanced/migration.rst diff --git a/doc/advanced/migration.rst b/doc/advanced/migration.rst new file mode 100644 index 0000000..c77b5d8 --- /dev/null +++ b/doc/advanced/migration.rst @@ -0,0 +1,131 @@ +=============== +From 0.x to 1.0 +=============== + +Don't Touch A Thing +=================== + +The first and simplest way to transition is to not transition: the 0.x +API won't be removed for a long time, possibly ever. While it is +unlikely to get updated any further and will eventually (hopefully?) +fall behind, if you can't be arsed you probably don't have to until an +unlikely 2.0. + +Unavoidable Divergences +======================= + +Although the 1.0 API aims to be reasonably bridge-able to +0.x-compatible code, a few items may be cause for concern and require +special attention: + +- The 1.0 API supports :data:`ua_parser.UserAgent.patch_minor + `, which may be a concern if the program stores + match results and compares them exactly. +- The global parser of the 1.0 API is instantiated lazily on-demand, + this may cause unexpected runtime loads and can be remediated by + explicitly accessing the parser on load e.g.:: + + import ua_parser + # force initialisation of global parser + ua_parser.parser + +- The 1.0 API defaults to an :class:`re2-based parser + ` if |re2|_ is installed, although it seems + unlikely you may wish to consider replacing it with configuring a + :class:`~ua_parser.Parser` with a :class:`ua_parser.basic.Resolver` + if |re2|_ is one of your dependencies. + +Default Ruleset +=============== + +While the 1.0 API was designed to better respect :pep:`8` and support +:mod:`typing`, it was also designed to easily be transitioned from. + +Given a 0.x API not using YAML, the conversion should be very easy and +consists of: + +- updating the import from ``ua_parser.user_agent_parser`` to just + ``ua_parser`` +- lowercasing the function names +- adding :meth:`~ua_parser.Result.with_defaults()` after the function + call +- wrapping the entire thing in :func:`dataclasses.asdict` + +For example, given + +.. + >>> from pprint import pprint + >>> ua_string = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36' + +.. code-block:: pycon + + >>> from ua_parser import user_agent_parser + >>> pprint(user_agent_parser.Parse(ua_string)) # doctest: +NORMALIZE_WHITESPACE + {'device': {'brand': 'Apple', 'family': 'Mac', 'model': 'Mac'}, + 'os': {'family': 'Mac OS X', + 'major': '10', + 'minor': '9', + 'patch': '4', + 'patch_minor': None}, + 'string': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36', + 'user_agent': {'family': 'Chrome', + 'major': '41', + 'minor': '0', + 'patch': '2272'}} + +the 1.0 version would be: + +.. code-block:: pycon + + >>> import dataclasses + >>> import ua_parser + >>> pprint(dataclasses.asdict(ua_parser.parse(ua_string).with_defaults())) # doctest: +NORMALIZE_WHITESPACE + {'device': {'brand': 'Apple', 'family': 'Mac', 'model': 'Mac'}, + 'os': {'family': 'Mac OS X', + 'major': '10', + 'minor': '9', + 'patch': '4', + 'patch_minor': None}, + 'string': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36', + 'user_agent': {'family': 'Chrome', + 'major': '41', + 'minor': '0', + 'patch': '2272', + 'patch_minor': '104'}} + +.. note:: + + - by default, the 1.0 API simply leaves entries ``None`` when no + data was matched, :meth:`~ua_parser.Result.with_defaults()` + fills them with the default value for the domain matching the 0.x API + - the 1.0 API returns :class:`typed dataclasses rather than untyped + dicts `, hence the necessary conversion + to dict + +YAML Ruleset +============ + +The 1.0 API does not support :envvar:`UA_PARSER_YAML` anymore, instead +it provides a layered API which lets clients use multiple parsers at +the same time, and load rulesets from various datasources. + +Legacy YAML support can be added via a pretty small shim:: + + import ua_parser + from ua_parser.loaders import load_yaml + + if yaml_path = os.environ.get("UA_PARSER_YAML"): + ua_parser.parser = ua_parser.Parser.from_matchers( + load_yaml(yaml_path)) + +This will load the YAML file, create a parser from it, and set the new +parser as the global parser used by the top-level utility functions. + +.. seealso:: + + - :ref:`guide-custom-rulesets` + - :ref:`guide-custom-global-parser` + - :ref:`api-loading` + diff --git a/doc/api.rst b/doc/api.rst index 4c8fa8f..18a7d48 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -59,7 +59,7 @@ Base Resolvers -------------- Base resolvers take sets of :class:`~ua_parser.core.Matchers` -generated by :ref:`loaders `, and use them to extract data +generated by :ref:`loaders `, and use them to extract data from user agent strings. .. autoclass:: ua_parser.basic.Resolver(Matchers) @@ -129,7 +129,7 @@ basic resolver `. .. autoclass:: Local -.. _loading: +.. _api-loading: Loading ------- diff --git a/doc/guides.rst b/doc/guides.rst index 2e88092..b216d18 100644 --- a/doc/guides.rst +++ b/doc/guides.rst @@ -2,6 +2,8 @@ Guides ====== +.. _guide-custom-rulesets: + Custom Rulesets =============== @@ -36,6 +38,8 @@ ua-parser provides easy ways to load custom rolesets: parser = Parser.from_matchers(load_yaml("regexes.yaml")) parser.parse(some_ua) +.. _guide-custom-global-parser: + Custom Global Parser ==================== diff --git a/doc/index.rst b/doc/index.rst index 72faa5b..65f7821 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -17,3 +17,4 @@ For more detailed insight and advanced uses, see the :doc:`api` and guides api advanced/caches + advanced/migration