You've already forked hotpocket
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from .associations import AssociationsSearchMode # noqa: F401
|
||||
from .core import NULL_UUID, App, Env # noqa: F401
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
|
||||
|
||||
class AssociationsSearchMode(enum.Enum):
|
||||
DEFAULT = ''
|
||||
STARRED = 'STARRED'
|
||||
ARCHIVED = 'ARCHIVED'
|
||||
19
services/packages/common/hotpocket_common/constants/core.py
Normal file
19
services/packages/common/hotpocket_common/constants/core.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
|
||||
NULL_UUID = uuid.UUID('00000000-0000-0000-0000-000000000000')
|
||||
|
||||
|
||||
class Env(enum.Enum):
|
||||
METAL = 'metal'
|
||||
DOCKER = 'docker'
|
||||
DEPLOYMENT = 'deployment'
|
||||
AIO = 'aio'
|
||||
|
||||
|
||||
class App(enum.Enum):
|
||||
ADMIN = 'admin'
|
||||
WEBAPP = 'webapp'
|
||||
53
services/packages/common/hotpocket_common/db/postgres.py
Normal file
53
services/packages/common/hotpocket_common/db/postgres.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from django.db.models.fields import Field
|
||||
from django.db.models.lookups import PatternLookup
|
||||
|
||||
|
||||
class BaseLikeLookup(PatternLookup):
|
||||
"""
|
||||
This class provides base for custom LIKE/ILIKE lookups for PostgreSQL.
|
||||
Unlike the builtin `contains` and `icontains`, these lookups do not do
|
||||
any DB-side processing of parameters. This opens up the ability to use
|
||||
index ops provided by `pg_trgm` to better index text columns for pattern
|
||||
searches.
|
||||
|
||||
These lookups currently support only PostgreSQL 15+.
|
||||
"""
|
||||
|
||||
# This is the pattern operator used to generate SQL for the lookup
|
||||
# when the right hand side isn't a raw string. Subclasses must specify this
|
||||
# or the whole thing will blow up ;).
|
||||
PATTERN_OP: str | None = None
|
||||
|
||||
# This is the rhs operator used to generate SQL for the lookup when the
|
||||
# right hand side is a raw string. Subclasses must specify this or the
|
||||
# whole thing will blow up ;).
|
||||
RHS_OP: str | None = None
|
||||
|
||||
def get_rhs_op(self, connection: typing.Any, rhs: typing.Any) -> typing.Any:
|
||||
if hasattr(self.rhs, 'as_sql') or self.bilateral_transforms:
|
||||
pattern = typing.cast(str, self.PATTERN_OP).format(
|
||||
connection.pattern_esc,
|
||||
)
|
||||
|
||||
return pattern.format(rhs)
|
||||
else:
|
||||
return typing.cast(str, self.RHS_OP)
|
||||
|
||||
|
||||
@Field.register_lookup
|
||||
class Like(BaseLikeLookup):
|
||||
PATTERN_OP = "LIKE '%%' || {} || '%%'"
|
||||
RHS_OP = 'LIKE %s'
|
||||
lookup_name = 'like'
|
||||
|
||||
|
||||
@Field.register_lookup
|
||||
class ILike(BaseLikeLookup):
|
||||
PATTERN_OP = "ILIKE '%%' || {} || '%%'"
|
||||
RHS_OP = 'ILIKE %s'
|
||||
lookup_name = 'ilike'
|
||||
15
services/packages/common/hotpocket_common/loader.py
Normal file
15
services/packages/common/hotpocket_common/loader.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import typing
|
||||
|
||||
|
||||
def load_module_attribute(import_path: str) -> typing.Any:
|
||||
module_path, attribute = import_path.split(':')
|
||||
|
||||
module = importlib.import_module(module_path)
|
||||
if attribute is not None:
|
||||
return getattr(module, attribute)
|
||||
|
||||
return module
|
||||
0
services/packages/common/hotpocket_common/py.typed
Normal file
0
services/packages/common/hotpocket_common/py.typed
Normal file
164
services/packages/common/hotpocket_common/url.py
Normal file
164
services/packages/common/hotpocket_common/url.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import typing
|
||||
import urllib.parse
|
||||
|
||||
|
||||
class URL:
|
||||
"""
|
||||
The URL class.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url = URL('https://ziomek.dog/').\
|
||||
set_path('/index_old.html').\
|
||||
set_query({
|
||||
'test': 'true',
|
||||
}).\
|
||||
to_string()
|
||||
|
||||
The *encoding* argument will be used to decode *initial* if it's
|
||||
```bytes``. Also, it'll be used when decoding QS.
|
||||
"""
|
||||
def __init__(self,
|
||||
initial: str | bytes | pathlib.Path | urllib.parse.ParseResult,
|
||||
encoding: str = 'utf-8'):
|
||||
self.encoding = encoding
|
||||
self.parsed_url: urllib.parse.ParseResult
|
||||
|
||||
if isinstance(initial, urllib.parse.ParseResult):
|
||||
self.parsed_url = initial
|
||||
elif isinstance(initial, pathlib.Path):
|
||||
self.parsed_url = urllib.parse.urlparse(str(initial))
|
||||
elif isinstance(initial, bytes):
|
||||
self.parsed_url = urllib.parse.urlparse(
|
||||
initial.decode(self.encoding),
|
||||
)
|
||||
else:
|
||||
self.parsed_url = urllib.parse.urlparse(initial)
|
||||
|
||||
def replace_parsed_url_field(self, field: str, value: typing.Any):
|
||||
self.parsed_url = self.parsed_url._replace(**{field: value})
|
||||
|
||||
return self
|
||||
|
||||
def append_path_component(self, component: str | pathlib.Path) -> URL:
|
||||
_ = self.set_path(self.path / component)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def scheme(self) -> str:
|
||||
return self.parsed_url.scheme
|
||||
|
||||
def set_scheme(self, new_scheme: str) -> URL:
|
||||
self.replace_parsed_url_field('scheme', new_scheme)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def netloc(self) -> str:
|
||||
return self.parsed_url.netloc
|
||||
|
||||
def set_netloc(self, new_netloc: str) -> URL:
|
||||
self.replace_parsed_url_field('netloc', new_netloc)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def hostname(self) -> str | None:
|
||||
return self.parsed_url.hostname
|
||||
|
||||
@property
|
||||
def port(self) -> int | None:
|
||||
return self.parsed_url.port
|
||||
|
||||
@property
|
||||
def username(self) -> str | None:
|
||||
return self.parsed_url.username
|
||||
|
||||
@property
|
||||
def password(self) -> str | None:
|
||||
return self.parsed_url.password
|
||||
|
||||
@property
|
||||
def path(self) -> pathlib.PurePosixPath:
|
||||
return pathlib.PurePosixPath(self.parsed_url.path)
|
||||
|
||||
def set_path(self, new_path: str | pathlib.Path | pathlib.PurePath) -> URL:
|
||||
self.replace_parsed_url_field(
|
||||
'path', str(pathlib.PurePosixPath(new_path)),
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def params(self) -> str:
|
||||
return self.parsed_url.params
|
||||
|
||||
def set_params(self, new_params: str) -> URL:
|
||||
self.replace_parsed_url_field('params', new_params)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def query(self) -> dict[typing.AnyStr, list[typing.AnyStr]]:
|
||||
if self.parsed_url.query == '':
|
||||
return {}
|
||||
|
||||
return urllib.parse.parse_qs(
|
||||
self.parsed_url.query, encoding=self.encoding, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def set_query(self,
|
||||
new_query: dict[typing.AnyStr, list[typing.AnyStr]] | list,
|
||||
) -> URL:
|
||||
self.replace_parsed_url_field(
|
||||
'query', urllib.parse.urlencode(new_query, doseq=True),
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def query_string(self) -> str:
|
||||
return self.parsed_url.query
|
||||
|
||||
def set_query_string(self, new_query_string: str) -> URL:
|
||||
self.replace_parsed_url_field('query', new_query_string)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def fragment(self) -> str:
|
||||
return self.parsed_url.fragment
|
||||
|
||||
def set_fragment(self, new_fragment: str) -> URL:
|
||||
self.replace_parsed_url_field('fragment', new_fragment)
|
||||
|
||||
return self
|
||||
|
||||
def to_string(self) -> str:
|
||||
return urllib.parse.urlunparse(self.parsed_url)
|
||||
|
||||
def __truediv__(self, component: str | pathlib.Path) -> URL:
|
||||
return self.append_path_component(component)
|
||||
|
||||
def __mod__(self,
|
||||
query: dict[typing.AnyStr, list[typing.AnyStr]],
|
||||
) -> URL:
|
||||
return self.set_query(
|
||||
{**self.query, **query},
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.to_string()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<URL {self}>'
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.__str__().encode(self.encoding)
|
||||
14
services/packages/common/hotpocket_common/uuid.py
Normal file
14
services/packages/common/hotpocket_common/uuid.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
|
||||
from uuid6 import UUID
|
||||
|
||||
|
||||
def uuid7_from_timestamp(timestamp: int) -> UUID:
|
||||
# The code below was shamelessly taken from `uuid6-python` package.
|
||||
# Copyright (c) 2021 oittaa | MIT License
|
||||
uuid_int = ((timestamp * 1_000) & 0xFFFFFFFFFFFF) << 80
|
||||
uuid_int |= secrets.randbits(76)
|
||||
return UUID(int=uuid_int, version=7)
|
||||
Reference in New Issue
Block a user