unitysvc-core 0.2.31__tar.gz → 0.2.32__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitysvc_core-0.2.31/src/unitysvc_core.egg-info → unitysvc_core-0.2.32}/PKG-INFO +1 -1
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/pyproject.toml +1 -1
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/utils.py +52 -32
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32/src/unitysvc_core.egg-info}/PKG-INFO +1 -1
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_utils.py +65 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/LICENSE +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/README.md +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/setup.cfg +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/__init__.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/__init__.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/base.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/documents.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/listing_data.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/listing_v1.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/offering_data.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/offering_v1.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/pricing.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/promotion_data.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/provider_data.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/provider_v1.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/service.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/service_group_data.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/validators.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/py.typed +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/base.json +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/listing_v1.json +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/offering_v1.json +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/promotion_v1.json +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/provider_v1.json +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/service_group_v1.json +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/validator.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core.egg-info/SOURCES.txt +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core.egg-info/dependency_links.txt +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core.egg-info/requires.txt +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core.egg-info/top_level.txt +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_access_interface_routing.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_effective_price.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_hidden_path_skip.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_offering_data.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_pricing.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_provider_account_rate_limits.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_service_group.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_service_status.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_validator.py +0 -0
- {unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/tests/test_validators.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unitysvc-core"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.32"
|
|
8
8
|
description = "Shared data models and validation helpers for UnitySVC packages"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
|
|
@@ -11,6 +11,7 @@ and unitysvc backend, including:
|
|
|
11
11
|
|
|
12
12
|
import hashlib
|
|
13
13
|
import json
|
|
14
|
+
import mimetypes
|
|
14
15
|
import os
|
|
15
16
|
import tomllib
|
|
16
17
|
import warnings
|
|
@@ -104,52 +105,71 @@ def get_basename(filename: str) -> str:
|
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
def mime_type_to_extension(mime_type: str) -> str:
|
|
107
|
-
"""
|
|
108
|
+
"""File extension for a media type or a document-kind token.
|
|
109
|
+
|
|
110
|
+
Both forms arrive here. ``Document.mime_type`` is a :class:`MimeTypeEnum`,
|
|
111
|
+
whose values are document KINDS rather than media types -- ``png``,
|
|
112
|
+
``markdown``, ``python``, and ``url``, which is not a media type at all --
|
|
113
|
+
while other callers pass a real ``image/png``. A kind that fell through to
|
|
114
|
+
``txt`` meant a PNG stored under a ``.txt`` key and served as
|
|
115
|
+
``text/plain``, which browsers refuse to render under ``nosniff``.
|
|
116
|
+
|
|
117
|
+
Resolution order: the explicit table, then the stdlib (which knows every
|
|
118
|
+
image kind both as a media type and as an extension), then the media type's
|
|
119
|
+
subtype, then ``txt``. The fallback stays: an honest, uninformative
|
|
120
|
+
extension beats a guessed-wrong media type.
|
|
108
121
|
|
|
109
122
|
Args:
|
|
110
|
-
mime_type:
|
|
123
|
+
mime_type: A media type (``image/png``) or a kind token (``png``).
|
|
111
124
|
|
|
112
125
|
Returns:
|
|
113
|
-
File extension without dot
|
|
126
|
+
File extension without the dot.
|
|
114
127
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
if not mime_type:
|
|
129
|
+
return "txt"
|
|
130
|
+
|
|
131
|
+
value = mime_type.strip().lower()
|
|
132
|
+
if not value:
|
|
133
|
+
return "txt"
|
|
134
|
+
|
|
135
|
+
# Spelled out where the stdlib is absent or disagrees with what we store:
|
|
136
|
+
# the languages have no media type it knows, ``url`` is not a media type,
|
|
137
|
+
# and we store ``.md`` and ``.jpg`` rather than ``.markdown`` and ``.jpeg``.
|
|
138
|
+
explicit = {
|
|
117
139
|
"text": "txt",
|
|
118
140
|
"plain": "txt",
|
|
119
141
|
"text/plain": "txt",
|
|
120
|
-
"text/html": "html",
|
|
121
142
|
"text/markdown": "md",
|
|
122
|
-
"text/csv": "csv",
|
|
123
|
-
"application/json": "json",
|
|
124
|
-
"application/pdf": "pdf",
|
|
125
|
-
"application/xml": "xml",
|
|
126
|
-
"application/x-yaml": "yaml",
|
|
127
|
-
"application/octet-stream": "bin",
|
|
128
|
-
"image/png": "png",
|
|
129
|
-
"image/jpeg": "jpg",
|
|
130
|
-
"image/gif": "gif",
|
|
131
143
|
"markdown": "md",
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
144
|
+
"image/jpeg": "jpg",
|
|
145
|
+
"jpeg": "jpg",
|
|
146
|
+
"python": "py",
|
|
147
|
+
"javascript": "js",
|
|
148
|
+
"bash": "sh",
|
|
136
149
|
"yaml": "yaml",
|
|
137
|
-
"
|
|
150
|
+
"application/x-yaml": "yaml",
|
|
151
|
+
"application/octet-stream": "bin",
|
|
138
152
|
"url": "url",
|
|
139
153
|
}
|
|
154
|
+
if value in explicit:
|
|
155
|
+
return explicit[value]
|
|
156
|
+
|
|
157
|
+
if "/" in value:
|
|
158
|
+
guessed = mimetypes.guess_extension(value)
|
|
159
|
+
if guessed:
|
|
160
|
+
return guessed.lstrip(".")
|
|
161
|
+
subtype = value.split("/", 1)[1]
|
|
162
|
+
if subtype in explicit:
|
|
163
|
+
return explicit[subtype]
|
|
164
|
+
if mimetypes.guess_type(f"x.{subtype}")[0]:
|
|
165
|
+
return subtype
|
|
166
|
+
return "txt"
|
|
167
|
+
|
|
168
|
+
# A bare token that is already a real extension: png, svg, webp, pdf, json,
|
|
169
|
+
# html, csv, xml. Resolved rather than listed, so the table stays short.
|
|
170
|
+
if mimetypes.guess_type(f"x.{value}")[0]:
|
|
171
|
+
return value
|
|
140
172
|
|
|
141
|
-
# Try exact match first
|
|
142
|
-
mime_lower = mime_type.lower()
|
|
143
|
-
if mime_lower in mime_map:
|
|
144
|
-
return mime_map[mime_lower]
|
|
145
|
-
|
|
146
|
-
# Try to extract from mime type parts
|
|
147
|
-
if "/" in mime_lower:
|
|
148
|
-
_, subtype = mime_lower.split("/", 1)
|
|
149
|
-
if subtype in mime_map:
|
|
150
|
-
return mime_map[subtype]
|
|
151
|
-
|
|
152
|
-
# Default to txt
|
|
153
173
|
return "txt"
|
|
154
174
|
|
|
155
175
|
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
+
from unitysvc_core.models.base import MimeTypeEnum
|
|
8
9
|
from unitysvc_core.utils import (
|
|
9
10
|
DEFAULT_PRESET_FNS,
|
|
10
11
|
DataFileLoadWarning,
|
|
@@ -64,6 +65,70 @@ def test_mime_type_to_extension() -> None:
|
|
|
64
65
|
assert mime_type_to_extension("application/pdf") == "pdf"
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
@pytest.mark.parametrize(
|
|
69
|
+
("value", "expected"),
|
|
70
|
+
[
|
|
71
|
+
("image/png", "png"),
|
|
72
|
+
("image/jpeg", "jpg"),
|
|
73
|
+
("image/svg+xml", "svg"),
|
|
74
|
+
("image/webp", "webp"),
|
|
75
|
+
("text/markdown", "md"),
|
|
76
|
+
("text/plain", "txt"),
|
|
77
|
+
("application/json", "json"),
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
def test_a_media_type_resolves_to_its_extension(value: str, expected: str) -> None:
|
|
81
|
+
assert mime_type_to_extension(value) == expected
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.parametrize(
|
|
85
|
+
("value", "expected"),
|
|
86
|
+
[
|
|
87
|
+
("markdown", "md"),
|
|
88
|
+
("python", "py"),
|
|
89
|
+
("javascript", "js"),
|
|
90
|
+
("bash", "sh"),
|
|
91
|
+
("html", "html"),
|
|
92
|
+
("json", "json"),
|
|
93
|
+
("text", "txt"),
|
|
94
|
+
("pdf", "pdf"),
|
|
95
|
+
("jpeg", "jpg"),
|
|
96
|
+
("png", "png"),
|
|
97
|
+
("svg", "svg"),
|
|
98
|
+
("webp", "webp"),
|
|
99
|
+
("url", "url"),
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
def test_a_document_kind_token_resolves_to_its_extension(
|
|
103
|
+
value: str, expected: str
|
|
104
|
+
) -> None:
|
|
105
|
+
"""``Document.mime_type`` is a ``MimeTypeEnum``, whose values are kinds
|
|
106
|
+
rather than media types. A kind that fell through to ``txt`` meant a PNG
|
|
107
|
+
stored under a ``.txt`` key and served as ``text/plain``."""
|
|
108
|
+
assert mime_type_to_extension(value) == expected
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_every_mime_type_enum_value_resolves() -> None:
|
|
112
|
+
"""The regression this guards: an unknown kind became ``txt`` silently."""
|
|
113
|
+
unresolved = [
|
|
114
|
+
kind.value
|
|
115
|
+
for kind in MimeTypeEnum
|
|
116
|
+
if kind is not MimeTypeEnum.text and mime_type_to_extension(kind.value) == "txt"
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
assert unresolved == []
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.mark.parametrize("value", ["", " ", "not-a-thing", "application/x-unknown"])
|
|
123
|
+
def test_an_unresolvable_value_falls_back_to_txt(value: str) -> None:
|
|
124
|
+
assert mime_type_to_extension(value) == "txt"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_case_and_whitespace_are_not_the_callers_problem() -> None:
|
|
128
|
+
assert mime_type_to_extension("PNG") == "png"
|
|
129
|
+
assert mime_type_to_extension(" image/png ") == "png"
|
|
130
|
+
|
|
131
|
+
|
|
67
132
|
# =============================================================================
|
|
68
133
|
# deep_merge_dicts
|
|
69
134
|
# =============================================================================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/models/service_group_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core/schema/service_group_v1.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unitysvc_core-0.2.31 → unitysvc_core-0.2.32}/src/unitysvc_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|