core-framework 0.12.1__py3-none-any.whl → 0.12.2__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.
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  from abc import ABC, abstractmethod
11
11
  from dataclasses import dataclass, field
12
- from typing import Any, Callable, TYPE_CHECKING
12
+ from typing import Any, Callable, ClassVar, TYPE_CHECKING
13
13
  from collections.abc import Awaitable
14
14
 
15
15
  from sqlalchemy import text
@@ -31,9 +31,90 @@ class ColumnDef:
31
31
  unique: bool = False
32
32
  index: bool = False
33
33
 
34
+ # Mapeamento de tipos genéricos para tipos específicos de cada dialeto
35
+ # Bug #1: DATETIME → TIMESTAMP para PostgreSQL
36
+ TYPE_MAPPING: ClassVar[dict[str, dict[str, str]]] = {
37
+ "postgresql": {
38
+ "DATETIME": "TIMESTAMP WITH TIME ZONE",
39
+ "TIMESTAMP": "TIMESTAMP WITH TIME ZONE",
40
+ "BOOLEAN": "BOOLEAN",
41
+ "TINYINT": "SMALLINT",
42
+ "LONGTEXT": "TEXT",
43
+ "DOUBLE": "DOUBLE PRECISION",
44
+ },
45
+ "mysql": {
46
+ "DATETIME": "DATETIME",
47
+ "TIMESTAMP": "TIMESTAMP",
48
+ "BOOLEAN": "TINYINT(1)",
49
+ "TEXT": "LONGTEXT",
50
+ "UUID": "CHAR(36)",
51
+ },
52
+ "sqlite": {
53
+ "DATETIME": "DATETIME",
54
+ "TIMESTAMP": "DATETIME",
55
+ "BOOLEAN": "BOOLEAN",
56
+ "UUID": "TEXT",
57
+ },
58
+ }
59
+
60
+ def _get_dialect_type(self, dialect: str) -> str:
61
+ """
62
+ Converte tipo genérico para tipo específico do dialeto.
63
+
64
+ Args:
65
+ dialect: Nome do dialeto (postgresql, mysql, sqlite)
66
+
67
+ Returns:
68
+ Tipo SQL específico para o dialeto
69
+ """
70
+ # Extrai tipo base (sem parâmetros como VARCHAR(255))
71
+ base_type = self.type.split("(")[0].upper()
72
+
73
+ # Verifica se há mapeamento específico
74
+ dialect_mapping = self.TYPE_MAPPING.get(dialect, {})
75
+ mapped_type = dialect_mapping.get(base_type)
76
+
77
+ if mapped_type:
78
+ # Se o tipo original tinha parâmetros, tenta preservá-los
79
+ if "(" in self.type and "(" not in mapped_type:
80
+ # Tipo como VARCHAR(255) mantém os parâmetros
81
+ return self.type
82
+ return mapped_type
83
+
84
+ return self.type
85
+
86
+ def _get_default_sql(self, dialect: str) -> str | None:
87
+ """
88
+ Gera SQL para valor default considerando o dialeto.
89
+
90
+ Bug #2: Boolean defaults usando TRUE/FALSE para PostgreSQL
91
+
92
+ Args:
93
+ dialect: Nome do dialeto
94
+
95
+ Returns:
96
+ String SQL para o default ou None
97
+ """
98
+ if self.default is None:
99
+ return None
100
+
101
+ if isinstance(self.default, str):
102
+ return f"DEFAULT '{self.default}'"
103
+
104
+ if isinstance(self.default, bool):
105
+ # PostgreSQL exige TRUE/FALSE em vez de 1/0
106
+ if dialect == "postgresql":
107
+ return f"DEFAULT {'TRUE' if self.default else 'FALSE'}"
108
+ else:
109
+ return f"DEFAULT {1 if self.default else 0}"
110
+
111
+ return f"DEFAULT {self.default}"
112
+
34
113
  def to_sql(self, dialect: str = "sqlite") -> str:
35
- """Gera SQL para a coluna."""
36
- parts = [f'"{self.name}"', self.type]
114
+ """Gera SQL para a coluna, adaptado ao dialeto do banco."""
115
+ # Obtém tipo adaptado ao dialeto
116
+ col_type = self._get_dialect_type(dialect)
117
+ parts = [f'"{self.name}"', col_type]
37
118
 
38
119
  if self.primary_key:
39
120
  parts.append("PRIMARY KEY")
@@ -49,13 +130,10 @@ class ColumnDef:
49
130
  if not self.nullable and not self.primary_key:
50
131
  parts.append("NOT NULL")
51
132
 
52
- if self.default is not None:
53
- if isinstance(self.default, str):
54
- parts.append(f"DEFAULT '{self.default}'")
55
- elif isinstance(self.default, bool):
56
- parts.append(f"DEFAULT {1 if self.default else 0}")
57
- else:
58
- parts.append(f"DEFAULT {self.default}")
133
+ # Gera default adaptado ao dialeto
134
+ default_sql = self._get_default_sql(dialect)
135
+ if default_sql:
136
+ parts.append(default_sql)
59
137
 
60
138
  if self.unique and not self.primary_key:
61
139
  parts.append("UNIQUE")
core/views.py CHANGED
@@ -45,6 +45,48 @@ InputT = TypeVar("InputT", bound=InputSchema)
45
45
  OutputT = TypeVar("OutputT", bound=OutputSchema)
46
46
 
47
47
 
48
+ # =============================================================================
49
+ # Decorator para actions customizadas
50
+ # =============================================================================
51
+
52
+ def action(
53
+ methods: list[str] | None = None,
54
+ detail: bool = False,
55
+ url_path: str | None = None,
56
+ url_name: str | None = None,
57
+ permission_classes: list[type[Permission]] | None = None,
58
+ **kwargs: Any,
59
+ ):
60
+ """
61
+ Decorator para definir actions customizadas em ViewSets.
62
+
63
+ Exemplo:
64
+ class UserViewSet(ModelViewSet):
65
+ model = User
66
+
67
+ @action(methods=["POST"], detail=True)
68
+ async def activate(self, request: Request, db: AsyncSession, **kwargs):
69
+ user = await self.get_object(db, **kwargs)
70
+ user.is_active = True
71
+ await user.save(db)
72
+ return {"message": "User activated"}
73
+ """
74
+ def decorator(func):
75
+ func.is_action = True
76
+ func.methods = methods or ["GET"]
77
+ func.detail = detail
78
+ func.url_path = url_path or func.__name__
79
+ func.url_name = url_name or func.__name__
80
+ func.permission_classes = permission_classes
81
+ func.kwargs = kwargs
82
+ return func
83
+ return decorator
84
+
85
+
86
+ # =============================================================================
87
+ # Views
88
+ # =============================================================================
89
+
48
90
  class APIView:
49
91
  """
50
92
  View baseada em classe, similar ao DRF APIView.
@@ -799,36 +841,419 @@ class ReadOnlyModelViewSet(ViewSet[ModelT, InputT, OutputT]):
799
841
  raise HTTPException(status_code=405, detail="Method not allowed")
800
842
 
801
843
 
802
- # Decorator para actions customizadas
803
- def action(
804
- methods: list[str] | None = None,
805
- detail: bool = False,
806
- url_path: str | None = None,
807
- url_name: str | None = None,
808
- permission_classes: list[type[Permission]] | None = None,
809
- **kwargs: Any,
810
- ):
844
+ # =============================================================================
845
+ # Presets de ViewSet
846
+ # =============================================================================
847
+
848
+ class CreateModelViewSet(ViewSet[ModelT, InputT, OutputT]):
811
849
  """
812
- Decorator para definir actions customizadas em ViewSets.
850
+ ViewSet apenas para criação.
813
851
 
814
852
  Exemplo:
815
- class UserViewSet(ModelViewSet):
816
- model = User
853
+ class ContactFormViewSet(CreateModelViewSet):
854
+ model = ContactMessage
855
+ input_schema = ContactInput
856
+ output_schema = ContactOutput
857
+ """
858
+
859
+ async def list(self, *args: Any, **kwargs: Any) -> Any:
860
+ raise HTTPException(status_code=405, detail="Method not allowed")
861
+
862
+ async def retrieve(self, *args: Any, **kwargs: Any) -> Any:
863
+ raise HTTPException(status_code=405, detail="Method not allowed")
864
+
865
+ async def update(self, *args: Any, **kwargs: Any) -> Any:
866
+ raise HTTPException(status_code=405, detail="Method not allowed")
867
+
868
+ async def partial_update(self, *args: Any, **kwargs: Any) -> Any:
869
+ raise HTTPException(status_code=405, detail="Method not allowed")
870
+
871
+ async def destroy(self, *args: Any, **kwargs: Any) -> Any:
872
+ raise HTTPException(status_code=405, detail="Method not allowed")
873
+
874
+
875
+ class ListModelViewSet(ViewSet[ModelT, InputT, OutputT]):
876
+ """
877
+ ViewSet apenas para listagem.
878
+
879
+ Exemplo:
880
+ class PublicCategoryViewSet(ListModelViewSet):
881
+ model = Category
882
+ output_schema = CategoryOutput
883
+ """
884
+
885
+ async def create(self, *args: Any, **kwargs: Any) -> Any:
886
+ raise HTTPException(status_code=405, detail="Method not allowed")
887
+
888
+ async def retrieve(self, *args: Any, **kwargs: Any) -> Any:
889
+ raise HTTPException(status_code=405, detail="Method not allowed")
890
+
891
+ async def update(self, *args: Any, **kwargs: Any) -> Any:
892
+ raise HTTPException(status_code=405, detail="Method not allowed")
893
+
894
+ async def partial_update(self, *args: Any, **kwargs: Any) -> Any:
895
+ raise HTTPException(status_code=405, detail="Method not allowed")
896
+
897
+ async def destroy(self, *args: Any, **kwargs: Any) -> Any:
898
+ raise HTTPException(status_code=405, detail="Method not allowed")
899
+
900
+
901
+ class ListCreateModelViewSet(ViewSet[ModelT, InputT, OutputT]):
902
+ """
903
+ ViewSet para listagem e criação.
904
+
905
+ Exemplo:
906
+ class CommentViewSet(ListCreateModelViewSet):
907
+ model = Comment
908
+ input_schema = CommentInput
909
+ output_schema = CommentOutput
910
+ """
911
+
912
+ async def retrieve(self, *args: Any, **kwargs: Any) -> Any:
913
+ raise HTTPException(status_code=405, detail="Method not allowed")
914
+
915
+ async def update(self, *args: Any, **kwargs: Any) -> Any:
916
+ raise HTTPException(status_code=405, detail="Method not allowed")
917
+
918
+ async def partial_update(self, *args: Any, **kwargs: Any) -> Any:
919
+ raise HTTPException(status_code=405, detail="Method not allowed")
920
+
921
+ async def destroy(self, *args: Any, **kwargs: Any) -> Any:
922
+ raise HTTPException(status_code=405, detail="Method not allowed")
923
+
924
+
925
+ class RetrieveUpdateModelViewSet(ViewSet[ModelT, InputT, OutputT]):
926
+ """
927
+ ViewSet para recuperar e atualizar (sem delete).
928
+
929
+ Exemplo:
930
+ class ProfileViewSet(RetrieveUpdateModelViewSet):
931
+ model = Profile
932
+ input_schema = ProfileInput
933
+ output_schema = ProfileOutput
934
+ """
935
+
936
+ async def list(self, *args: Any, **kwargs: Any) -> Any:
937
+ raise HTTPException(status_code=405, detail="Method not allowed")
938
+
939
+ async def create(self, *args: Any, **kwargs: Any) -> Any:
940
+ raise HTTPException(status_code=405, detail="Method not allowed")
941
+
942
+ async def destroy(self, *args: Any, **kwargs: Any) -> Any:
943
+ raise HTTPException(status_code=405, detail="Method not allowed")
944
+
945
+
946
+ class RetrieveDestroyModelViewSet(ViewSet[ModelT, InputT, OutputT]):
947
+ """
948
+ ViewSet para recuperar e deletar.
949
+
950
+ Exemplo:
951
+ class NotificationViewSet(RetrieveDestroyModelViewSet):
952
+ model = Notification
953
+ output_schema = NotificationOutput
954
+ """
955
+
956
+ async def list(self, *args: Any, **kwargs: Any) -> Any:
957
+ raise HTTPException(status_code=405, detail="Method not allowed")
958
+
959
+ async def create(self, *args: Any, **kwargs: Any) -> Any:
960
+ raise HTTPException(status_code=405, detail="Method not allowed")
961
+
962
+ async def update(self, *args: Any, **kwargs: Any) -> Any:
963
+ raise HTTPException(status_code=405, detail="Method not allowed")
964
+
965
+ async def partial_update(self, *args: Any, **kwargs: Any) -> Any:
966
+ raise HTTPException(status_code=405, detail="Method not allowed")
967
+
968
+
969
+ class RetrieveUpdateDestroyModelViewSet(ViewSet[ModelT, InputT, OutputT]):
970
+ """
971
+ ViewSet para operações em item individual (sem list/create).
972
+
973
+ Exemplo:
974
+ class SettingsViewSet(RetrieveUpdateDestroyModelViewSet):
975
+ model = UserSettings
976
+ input_schema = SettingsInput
977
+ output_schema = SettingsOutput
978
+ """
979
+
980
+ async def list(self, *args: Any, **kwargs: Any) -> Any:
981
+ raise HTTPException(status_code=405, detail="Method not allowed")
982
+
983
+ async def create(self, *args: Any, **kwargs: Any) -> Any:
984
+ raise HTTPException(status_code=405, detail="Method not allowed")
985
+
986
+
987
+ class SearchModelViewSet(ModelViewSet[ModelT, InputT, OutputT]):
988
+ """
989
+ ModelViewSet com busca integrada.
990
+
991
+ Atributos:
992
+ search_fields: Campos para busca textual
993
+ filter_fields: Campos para filtros exatos
994
+ ordering_fields: Campos permitidos para ordenação
995
+ default_ordering: Ordenação padrão
996
+
997
+ Exemplo:
998
+ class ProductViewSet(SearchModelViewSet):
999
+ model = Product
1000
+ input_schema = ProductInput
1001
+ output_schema = ProductOutput
1002
+ search_fields = ["name", "description"]
1003
+ filter_fields = ["category_id", "is_active"]
1004
+ ordering_fields = ["name", "price", "created_at"]
1005
+ default_ordering = ["-created_at"]
1006
+ """
1007
+
1008
+ search_fields: list[str] = []
1009
+ filter_fields: list[str] = []
1010
+ ordering_fields: list[str] = []
1011
+ default_ordering: list[str] = ["-id"]
1012
+ search_param: str = "q"
1013
+
1014
+ async def list(
1015
+ self,
1016
+ request: Request,
1017
+ db: AsyncSession,
1018
+ page: int = 1,
1019
+ page_size: int | None = None,
1020
+ **kwargs: Any,
1021
+ ) -> dict[str, Any]:
1022
+ """Lista com busca, filtros e ordenação."""
1023
+ await self.check_permissions(request, "list")
1024
+
1025
+ page_size = min(page_size or self.page_size, self.max_page_size)
1026
+ offset = (page - 1) * page_size
1027
+
1028
+ queryset = self.get_queryset(db)
1029
+
1030
+ # Aplicar busca textual
1031
+ search_query = request.query_params.get(self.search_param)
1032
+ if search_query and self.search_fields:
1033
+ queryset = self._apply_search(queryset, search_query)
1034
+
1035
+ # Aplicar filtros
1036
+ queryset = self._apply_filters(queryset, request.query_params)
1037
+
1038
+ # Aplicar ordenação
1039
+ ordering = request.query_params.get("ordering")
1040
+ queryset = self._apply_ordering(queryset, ordering)
1041
+
1042
+ total = await queryset.count()
1043
+ objects = await queryset.offset(offset).limit(page_size).all()
1044
+
1045
+ output_schema = self.get_output_schema()
1046
+ items = [output_schema.model_validate(obj).model_dump() for obj in objects]
1047
+
1048
+ return {
1049
+ "items": items,
1050
+ "total": total,
1051
+ "page": page,
1052
+ "page_size": page_size,
1053
+ "pages": (total + page_size - 1) // page_size if page_size > 0 else 0,
1054
+ }
1055
+
1056
+ def _apply_search(self, queryset: Any, search_query: str) -> Any:
1057
+ """Aplica busca textual nos campos configurados."""
1058
+ from sqlalchemy import or_
1059
+
1060
+ if not self.search_fields:
1061
+ return queryset
1062
+
1063
+ conditions = []
1064
+ for field in self.search_fields:
1065
+ if hasattr(self.model, field):
1066
+ column = getattr(self.model, field)
1067
+ conditions.append(column.ilike(f"%{search_query}%"))
1068
+
1069
+ if conditions:
1070
+ return queryset.filter(or_(*conditions))
1071
+ return queryset
1072
+
1073
+ def _apply_filters(self, queryset: Any, params: Any) -> Any:
1074
+ """Aplica filtros exatos nos campos configurados."""
1075
+ for field in self.filter_fields:
1076
+ value = params.get(field)
1077
+ if value is not None:
1078
+ queryset = queryset.filter(**{field: value})
1079
+ return queryset
1080
+
1081
+ def _apply_ordering(self, queryset: Any, ordering: str | None) -> Any:
1082
+ """Aplica ordenação."""
1083
+ if ordering:
1084
+ fields = ordering.split(",")
1085
+ else:
1086
+ fields = self.default_ordering
1087
+
1088
+ for field in fields:
1089
+ desc = field.startswith("-")
1090
+ field_name = field.lstrip("-")
817
1091
 
818
- @action(methods=["POST"], detail=True)
819
- async def activate(self, request: Request, db: AsyncSession, **kwargs):
820
- user = await self.get_object(db, **kwargs)
821
- user.is_active = True
822
- await user.save(db)
823
- return {"message": "User activated"}
1092
+ if field_name not in self.ordering_fields and self.ordering_fields:
1093
+ continue
1094
+
1095
+ if hasattr(self.model, field_name):
1096
+ column = getattr(self.model, field_name)
1097
+ if desc:
1098
+ queryset = queryset.order_by(column.desc())
1099
+ else:
1100
+ queryset = queryset.order_by(column.asc())
1101
+
1102
+ return queryset
1103
+
1104
+
1105
+ class BulkModelViewSet(ModelViewSet[ModelT, InputT, OutputT]):
824
1106
  """
825
- def decorator(func):
826
- func.is_action = True
827
- func.methods = methods or ["GET"]
828
- func.detail = detail
829
- func.url_path = url_path or func.__name__
830
- func.url_name = url_name or func.__name__
831
- func.permission_classes = permission_classes
832
- func.kwargs = kwargs
833
- return func
834
- return decorator
1107
+ ModelViewSet com operações em lote.
1108
+
1109
+ Endpoints adicionais:
1110
+ POST /bulk-create - Criar múltiplos
1111
+ PATCH /bulk-update - Atualizar múltiplos
1112
+ DELETE /bulk-delete - Deletar múltiplos
1113
+
1114
+ Exemplo:
1115
+ class ProductViewSet(BulkModelViewSet):
1116
+ model = Product
1117
+ input_schema = ProductInput
1118
+ output_schema = ProductOutput
1119
+ bulk_max_items = 100
1120
+ """
1121
+
1122
+ bulk_max_items: int = 100
1123
+
1124
+ @action(methods=["POST"], detail=False, url_path="bulk-create")
1125
+ async def bulk_create(
1126
+ self,
1127
+ request: Request,
1128
+ db: AsyncSession,
1129
+ data: list[dict[str, Any]] | None = None,
1130
+ **kwargs: Any,
1131
+ ) -> dict[str, Any]:
1132
+ """Cria múltiplos objetos de uma vez."""
1133
+ await self.check_permissions(request, "create")
1134
+
1135
+ if not data:
1136
+ raise HTTPException(status_code=400, detail="No data provided")
1137
+
1138
+ if len(data) > self.bulk_max_items:
1139
+ raise HTTPException(
1140
+ status_code=400,
1141
+ detail=f"Maximum {self.bulk_max_items} items allowed"
1142
+ )
1143
+
1144
+ input_schema = self.get_input_schema()
1145
+ output_schema = self.get_output_schema()
1146
+ created = []
1147
+ errors = []
1148
+
1149
+ for i, item_data in enumerate(data):
1150
+ try:
1151
+ validated = input_schema.model_validate(item_data)
1152
+ data_dict = validated.model_dump()
1153
+ validated_data = await self.validate_data(data_dict, db, instance=None)
1154
+
1155
+ obj = self.model(**validated_data)
1156
+ await obj.save(db)
1157
+ created.append(output_schema.model_validate(obj).model_dump())
1158
+ except Exception as e:
1159
+ errors.append({"index": i, "error": str(e)})
1160
+
1161
+ return {
1162
+ "created": created,
1163
+ "created_count": len(created),
1164
+ "errors": errors,
1165
+ "error_count": len(errors),
1166
+ }
1167
+
1168
+ @action(methods=["PATCH"], detail=False, url_path="bulk-update")
1169
+ async def bulk_update(
1170
+ self,
1171
+ request: Request,
1172
+ db: AsyncSession,
1173
+ data: list[dict[str, Any]] | None = None,
1174
+ **kwargs: Any,
1175
+ ) -> dict[str, Any]:
1176
+ """Atualiza múltiplos objetos."""
1177
+ await self.check_permissions(request, "update")
1178
+
1179
+ if not data:
1180
+ raise HTTPException(status_code=400, detail="No data provided")
1181
+
1182
+ if len(data) > self.bulk_max_items:
1183
+ raise HTTPException(
1184
+ status_code=400,
1185
+ detail=f"Maximum {self.bulk_max_items} items allowed"
1186
+ )
1187
+
1188
+ output_schema = self.get_output_schema()
1189
+ updated = []
1190
+ errors = []
1191
+
1192
+ for i, item_data in enumerate(data):
1193
+ try:
1194
+ item_id = item_data.get(self.lookup_field)
1195
+ if not item_id:
1196
+ errors.append({"index": i, "error": f"Missing {self.lookup_field}"})
1197
+ continue
1198
+
1199
+ obj = await self.get_object(db, **{self.lookup_field: item_id})
1200
+
1201
+ update_data = {k: v for k, v in item_data.items() if k != self.lookup_field}
1202
+ for field, value in update_data.items():
1203
+ if hasattr(obj, field):
1204
+ setattr(obj, field, value)
1205
+
1206
+ await obj.save(db)
1207
+ updated.append(output_schema.model_validate(obj).model_dump())
1208
+ except HTTPException as e:
1209
+ errors.append({"index": i, "error": e.detail})
1210
+ except Exception as e:
1211
+ errors.append({"index": i, "error": str(e)})
1212
+
1213
+ return {
1214
+ "updated": updated,
1215
+ "updated_count": len(updated),
1216
+ "errors": errors,
1217
+ "error_count": len(errors),
1218
+ }
1219
+
1220
+ @action(methods=["DELETE"], detail=False, url_path="bulk-delete")
1221
+ async def bulk_delete(
1222
+ self,
1223
+ request: Request,
1224
+ db: AsyncSession,
1225
+ data: dict[str, Any] | None = None,
1226
+ **kwargs: Any,
1227
+ ) -> dict[str, Any]:
1228
+ """Deleta múltiplos objetos por IDs."""
1229
+ await self.check_permissions(request, "destroy")
1230
+
1231
+ if not data or "ids" not in data:
1232
+ raise HTTPException(status_code=400, detail="No ids provided")
1233
+
1234
+ ids = data["ids"]
1235
+ if len(ids) > self.bulk_max_items:
1236
+ raise HTTPException(
1237
+ status_code=400,
1238
+ detail=f"Maximum {self.bulk_max_items} items allowed"
1239
+ )
1240
+
1241
+ deleted = []
1242
+ errors = []
1243
+
1244
+ for item_id in ids:
1245
+ try:
1246
+ obj = await self.get_object(db, **{self.lookup_field: item_id})
1247
+ await obj.delete(db)
1248
+ deleted.append(item_id)
1249
+ except HTTPException as e:
1250
+ errors.append({"id": item_id, "error": e.detail})
1251
+ except Exception as e:
1252
+ errors.append({"id": item_id, "error": str(e)})
1253
+
1254
+ return {
1255
+ "deleted": deleted,
1256
+ "deleted_count": len(deleted),
1257
+ "errors": errors,
1258
+ "error_count": len(errors),
1259
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-framework
3
- Version: 0.12.1
3
+ Version: 0.12.2
4
4
  Summary: Core Framework - Django-inspired, FastAPI-powered. Alta performance, baixo acoplamento, produtividade extrema.
5
5
  Project-URL: Homepage, https://github.com/SorPuti/core-framework
6
6
  Project-URL: Documentation, https://github.com/SorPuti/core-framework#readme
@@ -1,12 +1,13 @@
1
- core/__init__.py,sha256=9ZROdddVYtn67JmyUQ9s9srG61sik4V6h08zZGeU0Y8,10488
2
- core/app.py,sha256=5iWh5lz_XB5qZeJo8Xx4hPy1px-dWURMIVGJYjX6W3A,18633
1
+ core/__init__.py,sha256=Ka6DK8M7VOnG9iIsEhx17UFdtVRaZGZD7EIttp1-GGE,12058
2
+ core/app.py,sha256=sCA3mJI696i7MIjrPxfOr5zEYt0njarQfHHy3EAajk4,21071
3
3
  core/choices.py,sha256=rhcL3p2dB7RK99zIilpmoTFVcibQEIaRpz0CY0kImCE,10502
4
- core/config.py,sha256=f3dPfCLyPC73Ejft5ru9x_BbyQyYRyE5PM8u-22rpUw,13072
4
+ core/config.py,sha256=2-MVF9nLoYmxpYYH_Gzn4-Sa3MU87YZskRPtlNyhg6Q,14049
5
5
  core/database.py,sha256=XqB5tZnb9UYDbVGIh96YbmbGJZMqln6-diPBHCr3VWk,11564
6
6
  core/datetime.py,sha256=bzqlAj3foA-lzbhXjlEiDNR2D-nwXu9mpxpdcUb-Pmw,32730
7
7
  core/dependencies.py,sha256=LrNLbmQhXoCPRvoRPRMGNo0w6_l4NLGWeeTHzpUU36M,11582
8
8
  core/exceptions.py,sha256=cdcffeYnMzCbS4hApOYNmPVNbPUpKcrgJbi3nKhqTuI,22702
9
9
  core/fields.py,sha256=F2NdToowkJ_LFvPN9KVyxIFES1AlVDy7WkEp-8UiBpA,9327
10
+ core/middleware.py,sha256=kQ8APXerC9fzKHkGhTjrTYfjAvfCpOtQE-IjCPurvxo,23172
10
11
  core/models.py,sha256=jdNdjRPKBZiOBOgg8CmDYBwmuWdD7twCIpqINLGY4Q4,33788
11
12
  core/permissions.py,sha256=HQu_eNBEodJyR50CYcFvdCw9LlEfhI5vJbljV5VIs7M,10162
12
13
  core/querysets.py,sha256=Z87-U06Un_xA9GKwcjXx0yzw6F_xf_tvG_rBT5UGL9c,22678
@@ -15,17 +16,18 @@ core/routing.py,sha256=vIiJN8bQ2836WW2zUKTJVBTC8RpjtDYgEGdz7mldnGc,15422
15
16
  core/serializers.py,sha256=gR5Y7wTACm1pECkUEpAKBUbPmONGLMDDwej4fyIiOdo,9438
16
17
  core/tenancy.py,sha256=bDnQCejlfhmpoeGhs4sOsvl0aiaz99ukYiutFv7BtgM,9185
17
18
  core/validators.py,sha256=LCDyvqwIKnMaUEdaVx5kWveZt3XsydknZ_bxBL4ic5U,27895
18
- core/views.py,sha256=rLE7lHgO6uY6DBKQiaC30_08hH_zpFKxf8os4oqye5A,28209
19
- core/auth/__init__.py,sha256=mCiS_LCK3mEhE1me8LcBzKIW1-MMIn036JOG9pRtr-I,3618
19
+ core/views.py,sha256=Vm2FREET0IJ2JZbClNJ0vvZ6RN5aQKC1sDXsrOb4-SY,43319
20
+ core/auth/__init__.py,sha256=hVBigKluu4NdEmS2fbSHS-ylg3t99q5IH0cdjFDJ3JU,4322
20
21
  core/auth/backends.py,sha256=R-siIE8TrNqDHkCx42zXN1WVvvuWOun1nj8D5elrC9g,10425
21
- core/auth/base.py,sha256=QqX_3MET9G6t9ge1-GuZeAUA7jvJ21macN9lALl9YJs,16661
22
+ core/auth/base.py,sha256=Q7vXgwTmgdmyW7G8eJmDket2bKB_8YFnraZ_kK9_gTs,21425
22
23
  core/auth/decorators.py,sha256=tmC7prKUvHuzQ3J872nM6r83DR9d82dCLXKLvUB1Os8,12288
23
24
  core/auth/hashers.py,sha256=0gIf67TU0k5H744FADpyh9_ugxA7m3mhYPZxLh_lEtc,12808
24
- core/auth/models.py,sha256=72Ti_w9r9-RcqyhGlra6ubxBd1x4i251nWZXgeDEEV4,28358
25
+ core/auth/middleware.py,sha256=pei2C1uy2WqyJeSGMYBwh0jj4Ong2I0_vmSi2y6F6jA,9425
26
+ core/auth/models.py,sha256=qT31MJoHOTbYJPr_KPKn4wCpSVC4vkgBHWFEfL-meG0,32654
25
27
  core/auth/permissions.py,sha256=v3ykAgNpq5wJ0NkuC_FuveMctOkDfM9Xp11XEnUAuBg,12461
26
- core/auth/schemas.py,sha256=-Va30mD7HV5Ylrk40y_7SlmUBfkuvwTvNJwWjbIU7g4,3273
28
+ core/auth/schemas.py,sha256=L0W96dOD348rJDGeu1K5Rz3aJj-GdwMr2vbwwsYfo2g,3469
27
29
  core/auth/tokens.py,sha256=jk-TnMRdVGPhy6pWqSF2Ef8RTqLrP6Mkuo5GvRQh9no,8489
28
- core/auth/views.py,sha256=voTr0yVmwcFhdFk3nsPvRBBvFFZ3RjTruu137ykorMA,9008
30
+ core/auth/views.py,sha256=n-WhSIVHJCsjyxBFrI2JCfy-kRpg4YybsOCPnbRpwWM,13277
29
31
  core/cli/__init__.py,sha256=obodnvfe8DUziqpk-IAaHTEOb1KSfYQeuBZEAofut4o,449
30
32
  core/cli/main.py,sha256=daWz8tuMkMYrkNBfueDH5OghncdqLs3k7BUMmvDsSvk,119635
31
33
  core/deployment/__init__.py,sha256=RNcBRO9oB3WRnhtTTwM6wzVEcUKpKF4XfRkGSbbykIc,794
@@ -61,7 +63,7 @@ core/migrations/analyzer.py,sha256=QiwG_Xf_-Mb-Kp4hstkF8xNJD0Tvxgz20vqvYZ6xEXM,2
61
63
  core/migrations/cli.py,sha256=mR3lIFTlXSvupFOPVlfuC-urJyDfNFR9nqYZn4TjIco,12019
62
64
  core/migrations/engine.py,sha256=tggCEV1FuFFyNkcGOlDZSryepUYHWfZ4irb0sbsWWZo,28821
63
65
  core/migrations/migration.py,sha256=Xv5MSNLvGAR9wnuMc4GRwciUSuU22AxWlWZP-hsVliI,2748
64
- core/migrations/operations.py,sha256=nLQzijKsSmzody8yezotT98bkffExlUQWu3rT0acWPY,26046
66
+ core/migrations/operations.py,sha256=wZLui76zU-MDiJfyn3l3NBRGJw1V4XF8tViSV3kvN6A,28651
65
67
  core/migrations/state.py,sha256=eb_EYTE1tG-xQIwliS_-QTgr0y8-Jj0Va4C3nfpMrd4,15324
66
68
  core/tasks/__init__.py,sha256=rDP4PD7Qtw8qbSbOtxMco9w2wBxRJl5uHiLUEDM0DYI,1662
67
69
  core/tasks/base.py,sha256=0EWEzWTez0iF6nlI7Aw3stZtBk0Cr7zZ9btI89YdWPU,11762
@@ -76,7 +78,7 @@ example/auth.py,sha256=zBpLutb8lVKnGfQqQ2wnyygsSutHYZzeJBuhnFhxBaQ,4971
76
78
  example/models.py,sha256=xKdx0kJ9n0tZ7sCce3KhV3BTvKvsh6m7G69eFm3ukf0,4549
77
79
  example/schemas.py,sha256=wJ9QofnuHp4PjtM_IuMMBLVFVDJ4YlwcF6uQm1ooKiY,6139
78
80
  example/views.py,sha256=GQwgQcW6yoeUIDbF7-lsaZV7cs8G1S1vGVtiwVpZIQE,14338
79
- core_framework-0.12.1.dist-info/METADATA,sha256=yFmET6_YLmBcnUp89W75EvuseWRZxBE-iTMS16vHTKU,12791
80
- core_framework-0.12.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
81
- core_framework-0.12.1.dist-info/entry_points.txt,sha256=lQ65IAOpieqU1VcHCUReeyandpyy8IKGix6IkJW_4Is,39
82
- core_framework-0.12.1.dist-info/RECORD,,
81
+ core_framework-0.12.2.dist-info/METADATA,sha256=ulG99_nW-of7nHPyV8Ybq4QEAVSyeHh2lqwmvy42XiE,12791
82
+ core_framework-0.12.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
83
+ core_framework-0.12.2.dist-info/entry_points.txt,sha256=lQ65IAOpieqU1VcHCUReeyandpyy8IKGix6IkJW_4Is,39
84
+ core_framework-0.12.2.dist-info/RECORD,,