File size: 4,770 Bytes
985c397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *   Copyright (c) 2025 Samuel Abels <knipknap@gmail.com>                  *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   This program is distributed in the hope that it will be useful,       *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************
from __future__ import annotations
import urllib.parse
from typing import Dict, Any, Mapping


class AssetUri:
    """
    Represents an asset URI with components.

    The URI structure is: <asset_type>://<asset_id>[/version]
    """

    def __init__(self, uri_string: str):
        # Manually parse the URI string
        parts = uri_string.split("://", 1)
        if len(parts) != 2:
            raise ValueError(f"Invalid URI structure: {uri_string}")

        self.asset_type = parts[0]
        rest = parts[1]

        # Split asset_id, version, and params
        path_and_query = rest.split("?", 1)
        path_parts = path_and_query[0].split("/")

        if not path_parts or not path_parts[0]:
            raise ValueError(f"Invalid URI structure: {uri_string}")

        self.asset_id = path_parts[0]
        self.version = path_parts[1] if len(path_parts) > 1 else None

        if len(path_parts) > 2:
            raise ValueError(f"Invalid URI path structure: {uri_string}")

        self.params: Dict[str, list[str]] = {}
        if len(path_and_query) > 1:
            self.params = urllib.parse.parse_qs(path_and_query[1])

        if not self.asset_type or not self.asset_id:
            raise ValueError(f"Invalid URI structure: {uri_string}")

    def __str__(self) -> str:
        path = f"/{self.version}" if self.version else ""

        query = urllib.parse.urlencode(self.params, doseq=True) if self.params else ""

        uri_string = urllib.parse.urlunparse((self.asset_type, self.asset_id, path, "", query, ""))
        return uri_string

    def __repr__(self) -> str:
        return f"AssetUri('{str(self)}')"

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, AssetUri):
            return NotImplemented
        return (
            self.asset_type == other.asset_type
            and self.asset_id == other.asset_id
            and self.version == other.version
            and self.params == other.params
        )

    def __hash__(self) -> int:
        """Returns a hash value for the AssetUri."""
        return hash((self.asset_type, self.asset_id, self.version, frozenset(self.params.items())))

    @classmethod
    def is_uri(cls, uri: AssetUri | str) -> bool:
        """Checks if the given string is a valid URI."""
        if isinstance(uri, AssetUri):
            return True

        try:
            AssetUri(uri)
        except ValueError:
            return False
        return True

    @staticmethod
    def build(
        asset_type: str,
        asset_id: str,
        version: str | None = None,
        params: Mapping[str, str | list[str]] | None = None,
    ) -> AssetUri:
        """Builds a Uri object from components."""
        uri = AssetUri.__new__(AssetUri)  # Create a new instance without calling __init__
        uri.asset_type = asset_type
        uri.asset_id = asset_id
        uri.version = version
        uri.params = {}
        if params:
            for key, value in params.items():
                if isinstance(value, list):
                    uri.params[key] = value
                else:
                    uri.params[key] = [value]
        return uri