Skip to content
This repository was archived by the owner on Jul 2, 2024. It is now read-only.

Commit d28fa4a

Browse files
authored
Update 2023.09.1 (#104)
- SonarCloud integration
2 parents c4326cd + acfb042 commit d28fa4a

File tree

10 files changed

+338
-115
lines changed

10 files changed

+338
-115
lines changed

.github/workflows/sonarcloud.yml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Collect coverage report for SonarCloud
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
types:
9+
- opened
10+
- synchronize
11+
- reopened
12+
branches:
13+
- master
14+
- devel
15+
16+
jobs:
17+
sonarcloud:
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v3
22+
with:
23+
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
24+
- name: Set up Python
25+
uses: actions/setup-python@v2
26+
with:
27+
python-version: "3.9"
28+
- name: Install dependencies
29+
run: |
30+
python -m pip install --upgrade pip
31+
pip install pytest pytest-cov
32+
- name: Install source packages
33+
run: |
34+
pip install -e .
35+
- name: Collect coverage
36+
run: |
37+
pytest --cov=src --cov-report=xml --cov-config=pyproject.toml --cov-branch
38+
- name: Upload coverage report
39+
uses: actions/upload-artifact@v2
40+
with:
41+
name: coverage
42+
path: ./coverage.xml
43+
- name: SonarCloud Scan
44+
uses: SonarSource/sonarcloud-github-action@master
45+
with:
46+
args:
47+
-Dsonar.projectKey=edu-python-course_problem-sets
48+
-Dsonar.organization=edu-python-course
49+
-Dsonar.python.coverage.reportPaths=coverage.xml -X
50+
-Dsonar.coverage.exclusions=**/scripts/**,**/__init__,**/__main__,**/tests/** -X
51+
env:
52+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
53+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ __pycache__
2424
# coverage reports
2525
*.coverage.db
2626
*.coverage
27+
coverage.*
2728

2829
# local databases
2930
pgdata/

poetry.lock

+103-102
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "problem-sets"
7-
version = "2023.08.1"
7+
version = "2023.09.1"
88
description = "Challenges and solutions for the Python training course"
99
license = "MIT"
1010
authors = [
@@ -67,10 +67,14 @@ addopts = "--cov=src --verbose"
6767
testpaths = ["tests"]
6868

6969
[tool.coverage.run]
70+
relative_files = true
71+
branch = true
7072
source = ["src"]
7173
omit = [
72-
"*__init__*",
73-
"*__main__*"
74+
"**/__init__",
75+
"**/__main__",
76+
"**/scripts/**",
77+
"**/tests/**",
7478
]
7579

7680
[tool.coverage.report]
@@ -81,10 +85,11 @@ exclude_lines = [
8185

8286
[tool.mypy]
8387
files = "src,tests"
88+
exclude = "scripts"
8489

8590
[tool.pylint."MASTER"]
8691
fail-under = 8
87-
ignore = ["tests"]
92+
ignore = ["tests", "scripts"]
8893
persistent = true
8994

9095
[tool.pylint."MESSAGES CONTROL"]

src/basics/__init__.py

Whitespace-only changes.

src/basics/scripts/__init__.py

Whitespace-only changes.

src/basics/scripts/demo_mutable.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
This module demonstrates the mutable nature of objects
3+
4+
"""
5+
6+
import logging
7+
from typing import Any, List, Optional
8+
9+
logging.basicConfig(level=logging.DEBUG)
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def change_mutable_v1(value: Any, mutable: List[Any]) -> List[Any]:
14+
"""This function will change mutable argument"""
15+
16+
mutable.append(value)
17+
return mutable
18+
19+
20+
# noinspection PyDefaultArgument
21+
def change_mutable_v2(value: Any,
22+
mutable: Optional[List[Any]] = []) -> List[Any]:
23+
"""This function will change default mutable object"""
24+
25+
mutable.append(value)
26+
return mutable
27+
28+
29+
def change_valid(value: Any,
30+
mutable: Optional[List[Any]] = None) -> List[Any]:
31+
"""
32+
This function wouldn't change defaults,
33+
but will change mutable argument.
34+
"""
35+
36+
mutable = mutable or []
37+
mutable.append(value)
38+
39+
return mutable
40+
41+
42+
if __name__ == "__main__":
43+
mutable_obj1 = []
44+
change_mutable_v1(1, mutable_obj1)
45+
mutable_obj1 += [2] # [1, 2], because it was changed by "change_mutable"
46+
47+
logger.info(f"{mutable_obj1 = }")
48+
49+
mutable_obj2 = change_mutable_v2(10) # expected: [10]
50+
mutable_obj3 = change_mutable_v2(20) # expected: [20], but [10, 20]
51+
52+
logger.info(f"{mutable_obj2 = }")
53+
logger.info(f"{mutable_obj3 = }")
54+
55+
mutable_obj4 = change_valid(100) # expected: [100]
56+
mutable_obj5 = change_valid(200) # expected: [200]
57+
mutable_obj6 = change_valid(3, mutable_obj1) # expected: [1, 2, 3]
58+
59+
logger.info(f"{mutable_obj4 = }")
60+
logger.info(f"{mutable_obj5 = }")
61+
logger.info(f"{mutable_obj6 = }")

src/sequences/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
Balanced Parentheses
2626
--------------------
2727
28-
A parentheses is said to be balanced if each left parenthesis has its
28+
A parentheses are said to be balanced if each left parenthesis has its
2929
respective right parenthesis to match its pair in a well-nested format.
3030
3131
.. autofunction:: are_parentheses_balanced
@@ -39,4 +39,4 @@
3939
"get_longest_uniq_length",
4040
]
4141

42-
from .func import *
42+
from sequences.func import *

src/sequences/func.py

+86-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
44
"""
55

6-
import string
76
from typing import List, Union
87

98
from calc import get_squares
@@ -38,16 +37,15 @@ def is_palindrome(origin: Union[str, int], /) -> bool:
3837
"""
3938

4039
origin = str(origin).lower()
41-
skip_chars: str = string.punctuation + string.whitespace
4240
left: int = 0
4341
right: int = len(origin) - 1
4442

4543
while left < right:
46-
if origin[left] in skip_chars:
44+
if not origin[left].isalnum():
4745
left += 1
4846
continue
4947

50-
if origin[right] in skip_chars:
48+
if not origin[right].isalnum():
5149
right -= 1
5250
continue
5351

@@ -64,7 +62,7 @@ def get_longest_palindrome(origin: str, /) -> str:
6462
"""
6563
Return the longest palindrome substring from the given input
6664
67-
:param origin:
65+
:param origin: an origin string to find palindromes in
6866
:type origin: str
6967
7068
:return: the longest palindrome
@@ -80,6 +78,20 @@ def get_longest_palindrome(origin: str, /) -> str:
8078
size: int = len(origin)
8179

8280
def expand(left: int, right: int) -> int:
81+
"""
82+
83+
This inner function uses closure to "size" and "origin" objects
84+
from outer score, and tries to expand to left and right from
85+
the specified index of the origin string, until palindrome and
86+
expansion checks are pass.
87+
88+
:param left: left starting expansion index
89+
:type left: int
90+
:param right: right starting expansion index
91+
:type right: int
92+
93+
"""
94+
8395
while left >= 0 and right < size and origin[left] == origin[right]:
8496
left -= 1
8597
right += 1
@@ -123,10 +135,10 @@ def get_palindrome_squares(limit: int, /) -> List[int]:
123135
124136
Range begins with 0.
125137
126-
:param limit:
138+
:param limit: the range limitation value
127139
:type limit: int
128140
129-
:return:
141+
:return: a list of palindrome squares within a range
130142
:rtype: list
131143
132144
"""
@@ -205,3 +217,70 @@ def get_longest_uniq_length(origin: str, /) -> int:
205217
length = max(length, right_idx - left_idx + 1)
206218

207219
return length
220+
221+
222+
def add_spaces(origin: str) -> str:
223+
"""
224+
Return modified string with whitespaces before capital letters
225+
226+
An origin string may contain letters in upper case. The function removes
227+
any white spaces present in the initial data, and transform the string
228+
in a way to add spaces only before letters in upper case.
229+
230+
:param origin: an original string
231+
:rtype: str
232+
233+
:return: string with white spaces before capital letters
234+
:rtype: str
235+
236+
Usage:
237+
238+
>>> assert add_spaces("camelCase") == "camel Case"
239+
>>> assert add_spaces(" John Doe ") == "John Doe"
240+
>>> assert add_spaces("racecar") == "racecar"
241+
242+
"""
243+
244+
# remove white spaces, if any
245+
origin = origin.replace(" ", "")
246+
247+
return "".join(f" {c}" if c.isupper() else c for c in origin).lstrip()
248+
249+
250+
def get_consecutive_slices(origin: str, n: int) -> List[str]:
251+
"""
252+
Return possible slices of string as a collection consecutive lists
253+
254+
Given a string of digits and an integer `n`, this function returns all
255+
consecutive slices of length `n` from the string.
256+
257+
:param origin: the input string of digits
258+
:type origin: str
259+
:param n: the length of slices to be extracted
260+
:type n: int
261+
262+
:return: a list containing consecutive slices of length "n"
263+
:rtype: list
264+
265+
:raise: ValueError
266+
267+
Usage:
268+
269+
>>> assert get_consecutive_slices("0123", 1) == [[0], [1], [2], [3]]
270+
>>> assert get_consecutive_slices("0123", 2) == [[0, 1], [1, 2], [2, 3]]
271+
>>> assert get_consecutive_slices("0123", 3) == [[0, 1, 2], [1, 2, 3]]
272+
273+
"""
274+
275+
size: int = len(origin)
276+
if size < n:
277+
raise ValueError("slice size is bigger than origin length")
278+
279+
result = []
280+
idx = 0
281+
282+
while idx <= size - n:
283+
result.append(origin[idx:idx + n])
284+
idx += 1
285+
286+
return result

tests/sequences/func_test.py

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
import sequences
24

35

@@ -70,3 +72,24 @@ def test_get_longest_uniq_sequence_length():
7072
assert sequences.get_longest_uniq_length("abcdefg") == 7
7173
assert sequences.get_longest_uniq_length("abcacba") == 3
7274
assert sequences.get_longest_uniq_length("hwccjayhiszbmomlqkem") == 11
75+
76+
77+
def test_add_space():
78+
assert sequences.add_spaces("") == ""
79+
assert sequences.add_spaces("test_test") == "test_test"
80+
assert sequences.add_spaces("JohnDoe") == "John Doe"
81+
assert sequences.add_spaces("John Doe") == "John Doe"
82+
83+
84+
def test_get_consecutive_slices():
85+
assert sequences.get_consecutive_slices("0123", 1) == ["0", "1", "2", "3"]
86+
assert (
87+
sequences.get_consecutive_slices("0123", 2) == ["01", "12", "23"]
88+
)
89+
assert (
90+
sequences.get_consecutive_slices("0123", 3) == ["012", "123"]
91+
)
92+
93+
with pytest.raises(ValueError,
94+
match="slice size is bigger than origin length"):
95+
sequences.get_consecutive_slices("0123", 5)

0 commit comments

Comments
 (0)