minchoc 0.0.8__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of minchoc might be problematic. Click here for more details.
- minchoc/__init__.py +7 -0
- minchoc/admin.py +3 -0
- minchoc/apps.py +4 -0
- minchoc/constants.py +15 -2
- minchoc/filterlex.py +15 -4
- minchoc/filteryacc.py +113 -41
- minchoc/migrations/0002_alter_company_options_alter_package_unique_together_and_more.py +1 -1
- minchoc/models.py +38 -28
- minchoc/parsetab.py +348 -33
- minchoc/urls.py +5 -3
- minchoc/utils.py +18 -5
- minchoc/views.py +112 -65
- minchoc/wsgi.py +3 -0
- minchoc-0.1.0.dist-info/METADATA +114 -0
- minchoc-0.1.0.dist-info/RECORD +20 -0
- {minchoc-0.0.8.dist-info → minchoc-0.1.0.dist-info}/WHEEL +1 -1
- minchoc-0.1.0.dist-info/licenses/LICENSE.txt +18 -0
- minchoc-0.0.8.dist-info/LICENSE.txt +0 -21
- minchoc-0.0.8.dist-info/METADATA +0 -87
- minchoc-0.0.8.dist-info/RECORD +0 -20
minchoc/__init__.py
CHANGED
minchoc/admin.py
CHANGED
minchoc/apps.py
CHANGED
minchoc/constants.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
"""Constants."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
1
4
|
__all__ = ('FEED_XML_POST', 'FEED_XML_PRE')
|
|
2
5
|
|
|
3
|
-
FEED_XML_PRE =
|
|
6
|
+
FEED_XML_PRE = """<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
4
7
|
<feed xml:base="%(BASEURL)s/api/v1/"
|
|
5
8
|
xmlns="http://www.w3.org/2005/Atom"
|
|
6
9
|
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
|
|
@@ -8,5 +11,15 @@ FEED_XML_PRE = '''<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
|
8
11
|
<id>%(BASEURL)s/api/v1/Packages</id>
|
|
9
12
|
<title type="text">Packages</title>
|
|
10
13
|
<updated>%(UPDATED)s</updated>
|
|
11
|
-
<link rel="self" title="Packages" href="Packages" />
|
|
14
|
+
<link rel="self" title="Packages" href="Packages" />"""
|
|
15
|
+
"""
|
|
16
|
+
Feed XML preamble.
|
|
17
|
+
|
|
18
|
+
:meta hide-value:
|
|
19
|
+
"""
|
|
12
20
|
FEED_XML_POST = '</feed>'
|
|
21
|
+
"""
|
|
22
|
+
Feed XML file suffix.
|
|
23
|
+
|
|
24
|
+
:meta hide-value:
|
|
25
|
+
"""
|
minchoc/filterlex.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""Lexer."""
|
|
2
|
+
# ruff: noqa: D300,D400,N816
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
1
5
|
from typing import Any
|
|
2
6
|
|
|
3
7
|
from ply import lex
|
|
@@ -5,14 +9,21 @@ from ply import lex
|
|
|
5
9
|
__all__ = ('tokens',)
|
|
6
10
|
|
|
7
11
|
states = (('string', 'exclusive'),)
|
|
8
|
-
tokens = ('AND', 'EQ', 'ISLATESTVERSION', 'LPAREN', '
|
|
12
|
+
tokens = ('AND', 'COMMA', 'EQ', 'FIELD', 'ISLATESTVERSION', 'LPAREN', 'NE', 'NULL', 'OR', 'RPAREN',
|
|
13
|
+
'STRING', 'SUBSTRINGOF', 'TOLOWER')
|
|
9
14
|
|
|
10
15
|
t_AND = r'and'
|
|
16
|
+
t_COMMA = r','
|
|
11
17
|
t_EQ = r'eq'
|
|
18
|
+
t_FIELD = r'Description|Id|Tags'
|
|
12
19
|
t_ISLATESTVERSION = r'IsLatestVersion'
|
|
13
|
-
t_TOLOWER_ID = r'tolower\(Id\)'
|
|
14
20
|
t_LPAREN = r'\('
|
|
21
|
+
t_NE = r'ne'
|
|
22
|
+
t_NULL = r'null'
|
|
23
|
+
t_OR = r'or'
|
|
15
24
|
t_RPAREN = r'\)'
|
|
25
|
+
t_SUBSTRINGOF = r'substringof'
|
|
26
|
+
t_TOLOWER = r'tolower'
|
|
16
27
|
|
|
17
28
|
t_ignore = ' '
|
|
18
29
|
t_string_ignore = ' '
|
|
@@ -36,14 +47,14 @@ def t_string_escapedquote(_t: lex.LexToken) -> None:
|
|
|
36
47
|
def t_string_quote(t: lex.LexToken) -> lex.LexToken:
|
|
37
48
|
r"'"
|
|
38
49
|
t.value = t.lexer.lexdata[t.lexer.code_start:t.lexer.lexpos - 1]
|
|
39
|
-
t.type =
|
|
50
|
+
t.type = 'STRING'
|
|
40
51
|
t.lexer.lineno += t.value.count('\n')
|
|
41
52
|
t.lexer.begin('INITIAL')
|
|
42
53
|
return t
|
|
43
54
|
|
|
44
55
|
|
|
45
56
|
def t_string_error(t: lex.LexToken) -> None:
|
|
46
|
-
# print("illegal character '%s'" % t.value[0])
|
|
57
|
+
# print("illegal character '%s'" % t.value[0]) # noqa: ERA001
|
|
47
58
|
t.lexer.skip(1)
|
|
48
59
|
|
|
49
60
|
|
minchoc/filteryacc.py
CHANGED
|
@@ -1,56 +1,128 @@
|
|
|
1
|
-
|
|
1
|
+
"""Parser."""
|
|
2
|
+
# ruff: noqa: D205,D208,D209,D400,D403
|
|
3
|
+
from __future__ import annotations
|
|
2
4
|
|
|
3
|
-
from
|
|
4
|
-
from ply.lex import LexToken
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
5
6
|
|
|
7
|
+
from django.db.models import Q
|
|
6
8
|
from minchoc.filterlex import tokens # noqa: F401
|
|
9
|
+
from ply import yacc
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
14
|
+
from ply.lex import LexToken
|
|
15
|
+
|
|
16
|
+
__all__ = ('FIELD_MAPPING', 'parser')
|
|
17
|
+
|
|
18
|
+
FIELD_MAPPING = {'Description': 'description', 'Id': 'nuget_id', 'Tags': 'tags__name'}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def setup_p0(p: yacc.YaccProduction) -> None:
|
|
22
|
+
if not isinstance(p[0], Q): # pragma no cover
|
|
23
|
+
p[0] = Q()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def p_expression_expr(p: yacc.YaccProduction) -> None:
|
|
27
|
+
"""expression : LPAREN expression RPAREN"""
|
|
28
|
+
setup_p0(p)
|
|
29
|
+
p[0] = p[2]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def p_substringof(p: yacc.YaccProduction) -> None:
|
|
33
|
+
"""substringof : SUBSTRINGOF LPAREN STRING COMMA expression RPAREN"""
|
|
34
|
+
setup_p0(p)
|
|
35
|
+
a: str
|
|
36
|
+
b: Q
|
|
37
|
+
_, __, ___, a, ____, b, _____ = p
|
|
38
|
+
db_field = cast('Sequence[Any]', b.children[0])[0]
|
|
39
|
+
prefix = ''
|
|
40
|
+
if '__iexact' in db_field:
|
|
41
|
+
prefix = 'i'
|
|
42
|
+
db_field = db_field.replace('__iexact', '')
|
|
43
|
+
p[0] &= Q(**{f'{db_field}__{prefix}contains': a})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def p_tolower(p: yacc.YaccProduction) -> None:
|
|
47
|
+
"""tolower : TOLOWER LPAREN FIELD RPAREN"""
|
|
48
|
+
setup_p0(p)
|
|
49
|
+
field: Literal['Description', 'Id', 'Tags']
|
|
50
|
+
_, __, ___, field, ____ = p
|
|
51
|
+
p[0] &= Q(**{f'{FIELD_MAPPING[field]}__iexact': None})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def p_expression_field(p: yacc.YaccProduction) -> None:
|
|
55
|
+
"""expression : FIELD"""
|
|
56
|
+
setup_p0(p)
|
|
57
|
+
field: Literal['Description', 'Id', 'Tags']
|
|
58
|
+
_, field = p
|
|
59
|
+
p[0] &= Q(**{FIELD_MAPPING[field]: None})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class InvalidTypeForEq(Exception):
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
super().__init__('Only numbers and strings can be used with eq.')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def p_expression_op(p: yacc.YaccProduction) -> None:
|
|
68
|
+
"""expression : expression OR expression
|
|
69
|
+
| expression AND expression
|
|
70
|
+
| expression NE expression
|
|
71
|
+
| expression EQ expression""" # noqa: DOC501
|
|
72
|
+
setup_p0(p)
|
|
73
|
+
a: Q
|
|
74
|
+
b: Q | str
|
|
75
|
+
op: Literal['and', 'eq', 'ne', 'or']
|
|
76
|
+
_, a, op, b = p
|
|
77
|
+
if op == 'and':
|
|
78
|
+
assert isinstance(b, Q)
|
|
79
|
+
p[0] &= a & b
|
|
80
|
+
elif op == 'or':
|
|
81
|
+
assert isinstance(b, Q)
|
|
82
|
+
p[0] &= a | b
|
|
83
|
+
else:
|
|
84
|
+
db_field: str = cast('Sequence[Any]', a.children[0])[0]
|
|
85
|
+
if b == 'null' or (cast('Sequence[Any]', b.children[0])[0]
|
|
86
|
+
if isinstance(b, Q) else None) == 'rhs__isnull':
|
|
87
|
+
p[0] &= Q(**{f'{db_field}__isnull': op != 'ne'})
|
|
88
|
+
else: # eq
|
|
89
|
+
if not isinstance(b, int | str):
|
|
90
|
+
raise InvalidTypeForEq
|
|
91
|
+
p[0] &= Q(**{db_field: b})
|
|
92
|
+
|
|
7
93
|
|
|
8
|
-
|
|
94
|
+
def p_expression_str(p: yacc.YaccProduction) -> None:
|
|
95
|
+
"""expression : STRING"""
|
|
96
|
+
setup_p0(p)
|
|
97
|
+
s: str
|
|
98
|
+
_, s = p
|
|
99
|
+
p[0] = s
|
|
9
100
|
|
|
10
101
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return {}
|
|
15
|
-
return cast(dict[Any, Any], p[index])
|
|
16
|
-
except IndexError:
|
|
17
|
-
return {}
|
|
102
|
+
class GenericSyntaxError(SyntaxError):
|
|
103
|
+
def __init__(self, index: int, token: str) -> None:
|
|
104
|
+
super().__init__(f'Syntax error (index: {index}, token: "{token}")')
|
|
18
105
|
|
|
19
106
|
|
|
20
107
|
def p_expression(p: yacc.YaccProduction) -> None:
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
p
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"""expression : ISLATESTVERSION"""
|
|
35
|
-
p[0] = {
|
|
36
|
-
**dict_if_not_dict(p, 0),
|
|
37
|
-
**dict_if_not_dict(p, 1),
|
|
38
|
-
**dict_if_not_dict(p, 2), 'is_latest_version': True
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def p_tolower_id_eq_package(p: yacc.YaccProduction) -> None:
|
|
43
|
-
"""expression : TOLOWER_ID EQ STRING"""
|
|
44
|
-
p[0] = {
|
|
45
|
-
**dict_if_not_dict(p, 0),
|
|
46
|
-
**dict_if_not_dict(p, 1),
|
|
47
|
-
**dict_if_not_dict(p, 2), 'nuget_id__iexact': p[3]
|
|
48
|
-
}
|
|
108
|
+
"""expression : NULL
|
|
109
|
+
| substringof
|
|
110
|
+
| tolower
|
|
111
|
+
| ISLATESTVERSION"""
|
|
112
|
+
setup_p0(p)
|
|
113
|
+
expr: Any
|
|
114
|
+
_, expr = p
|
|
115
|
+
if expr == 'IsLatestVersion':
|
|
116
|
+
p[0] &= Q(is_latest_version=True)
|
|
117
|
+
elif expr == 'null':
|
|
118
|
+
p[0] &= Q(rhs__isnull=True)
|
|
119
|
+
else:
|
|
120
|
+
p[0] &= expr
|
|
49
121
|
|
|
50
122
|
|
|
51
123
|
def p_error(p: LexToken) -> None:
|
|
52
|
-
raise
|
|
124
|
+
raise GenericSyntaxError(p.lexer.lexpos, p.value)
|
|
53
125
|
|
|
54
126
|
|
|
55
|
-
#: An extremely basic parser for parsing ``$filter`` strings.
|
|
56
127
|
parser = yacc.yacc(debug=False)
|
|
128
|
+
"""An extremely basic parser for parsing ``$filter`` strings. Returns a ``Q`` instance."""
|
minchoc/models.py
CHANGED
|
@@ -1,56 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
"""Model definitions."""
|
|
2
|
+
# ruff: noqa: D106, DJ001
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
2
6
|
import uuid
|
|
3
7
|
|
|
4
8
|
from django.conf import settings
|
|
5
|
-
from django.contrib.auth.models import AbstractUser
|
|
6
9
|
from django.db import models
|
|
7
10
|
from django.db.models.signals import post_save
|
|
8
|
-
from
|
|
9
|
-
import
|
|
11
|
+
from django_stubs_ext.db.models import TypedModelMeta
|
|
12
|
+
from typing_extensions import override
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from django.contrib.auth.models import AbstractUser
|
|
16
|
+
from django.http import HttpRequest
|
|
12
17
|
|
|
13
18
|
__all__ = ('Author', 'Company', 'NugetUser', 'Package')
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class Company(models.Model):
|
|
17
22
|
"""Company associated to NuGet packages."""
|
|
18
|
-
|
|
23
|
+
name = models.CharField(max_length=255, unique=True)
|
|
24
|
+
|
|
25
|
+
class Meta(TypedModelMeta):
|
|
19
26
|
verbose_name = 'company'
|
|
20
27
|
verbose_name_plural = 'companies'
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
@override
|
|
24
30
|
def __str__(self) -> str:
|
|
25
31
|
return self.name
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
class NugetUser(models.Model):
|
|
29
35
|
"""An owner of a NuGet spec."""
|
|
30
|
-
base = models.OneToOneField(settings.AUTH_USER_MODEL,
|
|
31
|
-
on_delete=models.CASCADE) # type: ignore[var-annotated]
|
|
36
|
+
base = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
32
37
|
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
|
|
33
38
|
token = models.UUIDField()
|
|
34
39
|
|
|
40
|
+
@override
|
|
41
|
+
def __str__(self) -> str:
|
|
42
|
+
return cast('str', self.base.username)
|
|
43
|
+
|
|
35
44
|
@staticmethod
|
|
36
45
|
def token_exists(token: str | None) -> bool:
|
|
37
|
-
"""
|
|
38
|
-
return bool(token and NugetUser.
|
|
46
|
+
"""Check if a token exists."""
|
|
47
|
+
return bool(token and NugetUser._default_manager.filter(token=token).exists())
|
|
39
48
|
|
|
40
49
|
@staticmethod
|
|
41
50
|
def request_has_valid_token(request: HttpRequest) -> bool:
|
|
42
|
-
"""
|
|
43
|
-
Checks if the API key in the request (header ``X-NuGet-ApiKey``, case insensitive) is valid.
|
|
44
|
-
"""
|
|
51
|
+
"""Check if the API key in the request is valid."""
|
|
45
52
|
return NugetUser.token_exists(request.headers.get('X-NuGet-ApiKey'))
|
|
46
53
|
|
|
47
|
-
def __str__(self) -> str:
|
|
48
|
-
return cast(str, self.base.username)
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
def post_save_receiver(
|
|
56
|
+
sender: AbstractUser, # noqa: ARG001
|
|
57
|
+
instance: AbstractUser,
|
|
58
|
+
**kwargs: Any) -> None: # noqa: ARG001
|
|
59
|
+
"""Create a ``NugetUser`` when a new user is saved."""
|
|
60
|
+
if not NugetUser._default_manager.filter(base=instance).exists():
|
|
54
61
|
nuget_user = NugetUser()
|
|
55
62
|
nuget_user.base = instance
|
|
56
63
|
nuget_user.token = uuid.uuid4()
|
|
@@ -64,6 +71,7 @@ class Author(models.Model):
|
|
|
64
71
|
"""Author of the software, not the NuGet spec."""
|
|
65
72
|
name = models.CharField(max_length=255, unique=True)
|
|
66
73
|
|
|
74
|
+
@override
|
|
67
75
|
def __str__(self) -> str:
|
|
68
76
|
return self.name
|
|
69
77
|
|
|
@@ -72,18 +80,14 @@ class Tag(models.Model):
|
|
|
72
80
|
"""Tag associated to NuGet packages."""
|
|
73
81
|
name = models.CharField(max_length=128, unique=True)
|
|
74
82
|
|
|
83
|
+
@override
|
|
75
84
|
def __str__(self) -> str:
|
|
76
85
|
return self.name
|
|
77
86
|
|
|
78
87
|
|
|
79
88
|
class Package(models.Model):
|
|
80
89
|
"""An instance of a NuGet package."""
|
|
81
|
-
|
|
82
|
-
constraints = [
|
|
83
|
-
models.UniqueConstraint(fields=('nuget_id', 'version'), name='id_and_version_uniq')
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
authors = models.ManyToManyField(Author) # type: ignore[var-annotated]
|
|
90
|
+
authors = models.ManyToManyField(Author)
|
|
87
91
|
copyright = models.TextField(null=True)
|
|
88
92
|
dependencies = models.JSONField(null=True)
|
|
89
93
|
description = models.TextField(null=True)
|
|
@@ -100,7 +104,7 @@ class Package(models.Model):
|
|
|
100
104
|
nuget_id = models.CharField(max_length=128)
|
|
101
105
|
project_url = models.URLField()
|
|
102
106
|
published = models.DateTimeField(auto_now_add=True, blank=True)
|
|
103
|
-
references = models.JSONField(default=dict)
|
|
107
|
+
references = models.JSONField(default=dict)
|
|
104
108
|
release_notes = models.TextField(null=True)
|
|
105
109
|
require_license_acceptance = models.BooleanField(default=False)
|
|
106
110
|
size = models.PositiveIntegerField()
|
|
@@ -116,5 +120,11 @@ class Package(models.Model):
|
|
|
116
120
|
version3 = models.PositiveIntegerField(null=True)
|
|
117
121
|
version_beta = models.CharField(max_length=128, null=True)
|
|
118
122
|
|
|
123
|
+
class Meta(TypedModelMeta):
|
|
124
|
+
constraints = [ # noqa: RUF012
|
|
125
|
+
models.UniqueConstraint(fields=('nuget_id', 'version'), name='id_and_version_uniq')
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
@override
|
|
119
129
|
def __str__(self) -> str:
|
|
120
130
|
return f'{self.title} {self.version}'
|