Add basic at:// URI support

Changed files
+143 -2
src
atpasser
+29 -1
poetry.lock
··· 272 272 all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 273 273 274 274 [[package]] 275 + name = "jsonpath-ng" 276 + version = "1.7.0" 277 + description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." 278 + optional = false 279 + python-versions = "*" 280 + groups = ["main"] 281 + files = [ 282 + {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, 283 + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, 284 + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, 285 + ] 286 + 287 + [package.dependencies] 288 + ply = "*" 289 + 290 + [[package]] 275 291 name = "lxml" 276 292 version = "6.0.1" 277 293 description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." ··· 412 428 groups = ["main"] 413 429 files = [ 414 430 {file = "morphys-1.0-py2.py3-none-any.whl", hash = "sha256:76d6dbaa4d65f597e59d332c81da786d83e4669387b9b2a750cfec74e7beec20"}, 431 + ] 432 + 433 + [[package]] 434 + name = "ply" 435 + version = "3.11" 436 + description = "Python Lex & Yacc" 437 + optional = false 438 + python-versions = "*" 439 + groups = ["main"] 440 + files = [ 441 + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, 442 + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, 415 443 ] 416 444 417 445 [[package]] ··· 586 614 [metadata] 587 615 lock-version = "2.1" 588 616 python-versions = ">=3.13" 589 - content-hash = "f1ed7d854143c5a27897e3599cafa301e939d46f52b876acaae299f57fb47950" 617 + content-hash = "91faa3780c077d6f2d96269f782e106c25e374a61a51ecf7a8e56bfdf5564c1c"
+1 -1
pyproject.toml
··· 5 5 authors = [{ name = "diaowinner", email = "diaowinner@qq.com" }] 6 6 readme = "README.md" 7 7 requires-python = ">=3.13" 8 - dependencies = ["py-cid (>=0.3.0,<0.4.0)", "cbor2 (>=5.7.0,<5.8.0)", "dnspython (>=2.7.0,<3.0.0)", "requests (>=2.32.5,<3.0.0)", "pyld[requests] (>=2.0.4,<3.0.0)"] 8 + dependencies = ["py-cid (>=0.3.0,<0.4.0)", "cbor2 (>=5.7.0,<5.8.0)", "dnspython (>=2.7.0,<3.0.0)", "requests (>=2.32.5,<3.0.0)", "pyld[requests] (>=2.0.4,<3.0.0)", "jsonpath-ng (>=1.7.0,<2.0.0)"] 9 9 license = "MIT OR Apache-2.0" 10 10 license-files = ["LICEN[CS]E.*"] 11 11
+113
src/atpasser/uri/__init__.py
··· 1 + import urllib.parse as up 2 + from atpasser import handle, did 3 + import jsonpath_ng 4 + 5 + 6 + class URI: 7 + """ 8 + A class representing an AT URI. 9 + 10 + Attributes: 11 + uri (str): The AT URI. 12 + """ 13 + 14 + def __init__(self, uri: str) -> None: 15 + """ 16 + Initalizes an AT URI. 17 + 18 + Attributes: 19 + uri (str): The AT URI. 20 + """ 21 + 22 + if not set(uri).issubset(set([chr(i) for i in range(0x80)])): 23 + raise ValueError("invalid char in uri") 24 + 25 + if len(uri) > 8000: 26 + raise ValueError("uri longer than 8000 chars") # the doc says 8 "kilobytes" 27 + 28 + if not uri.startswith("at://"): 29 + raise ValueError("not starts with at://") 30 + 31 + burner = uri[5:].split("#") 32 + 33 + try: 34 + fragment = up.unquote(burner[1]) 35 + except: 36 + fragment = None 37 + 38 + try: 39 + query = up.unquote(burner[0].split("?")[1]) 40 + except: 41 + query = None 42 + 43 + path = [up.unquote(segment) for segment in burner[0].split("/")[1:]] 44 + if len(path) > 0 and path[-1] == "": 45 + raise ValueError("trailing slash") 46 + 47 + authorityValue = up.unquote(burner[0].split("/")[0]) 48 + 49 + p = up.urlparse(up.unquote("//" + authorityValue)) 50 + if p.username is not None or p.password is not None: 51 + raise ValueError("userinfo unsupported") 52 + 53 + # We decided not to detect if it's a handle there 54 + # 55 + # try: 56 + # authority = handle.Handle(authorityValue) 57 + # except: 58 + # try: 59 + # authority = did.DID(authorityValue) 60 + # except: 61 + # raise ValueError("authority is neither handle nor tid") 62 + 63 + if fragment != None: 64 + self.fragment = jsonpath_ng.parse(fragment) 65 + self.fragmentAsText = up.quote(fragment) 66 + 67 + if query != None: 68 + self.query = up.parse_qs(query) 69 + self.queryAsText = up.quote(query) 70 + else: 71 + self.query, self.queryAsText = None, None 72 + 73 + self.path = path 74 + pathAsText = "/".join([up.quote(i) for i in path]) 75 + self.pathAsText = pathAsText 76 + 77 + self.authorityAsText = authorityValue 78 + try: 79 + authority = handle.Handle(authorityValue) 80 + except: 81 + try: 82 + authority = did.DID(authorityValue) 83 + except: 84 + authority = None 85 + self.authority = authority 86 + 87 + unescapedAV = up.quote(authorityValue).replace("%3A", ":") 88 + self.uri = "at://{}/{}{}{}".format( 89 + unescapedAV, 90 + pathAsText, 91 + "?" + query if query != None else "", 92 + "?" + fragment if fragment != None else "", 93 + ) 94 + 95 + def __str__(self) -> str: 96 + """ 97 + 98 + Convert the RecordKey to a string by given the URI. 99 + """ 100 + return self.uri 101 + 102 + def __eq__(self, value: object, /) -> bool: 103 + """ 104 + 105 + Check if the 2 values are exactly the same. 106 + """ 107 + 108 + if isinstance(value, URI): 109 + 110 + return str(self) == str(value) 111 + else: 112 + 113 + raise TypeError